ui
This commit is contained in:
1 parent
da2677983b
commit
11964617a7
19 files changed
+160
-93
No files matched your search
@@ -14,25 +14,24 @@
|
||||
[x] config player to sep. row/popups?
|
||||
[x] dots: teacher settings fixes
|
||||
[x] ogs bot connector
|
||||
[x] clarify score change vs score
|
||||
[x] List edit settings/object edit settings? -> Mostly superseded by popups.
|
||||
|
||||
[/] README
|
||||
[] engine status
|
||||
[] sgf review improvements -- Likewise, in the 0.9 version, better alternatives to the played move were shown with squares, which was also pretty useful when using the sgf outside of Katrain. I mean, having the top move mentioned is all and good, but when you see multiple squares shown on the board as better alternatives to the move played in the game, it makes obvious how far from perfect that move actually was :D
|
||||
[] selective save dots / config
|
||||
[] Release notes
|
||||
|
||||
[] clarify score change vs score
|
||||
[] box to label ? split in status and comment?
|
||||
[] pv with overlap?
|
||||
|
||||
|
||||
Low priority
|
||||
[] more teaching / groups in danger? hard
|
||||
[] box to label ? split in status and comment?
|
||||
[] dual engine support -- easily possible but has weird effects on win rate etc
|
||||
[] When creating a new game, the 9 buttons on the right side aren't all that useful. Maybe the 9, 13 and 19 ones make sense since these three board sizes are the traditionally used ones, but why 2, 4 and 9 stones buttons? Why 0.5, 6.5 and -40pts komi buttons?
|
||||
[] Score instead of game end
|
||||
|
||||
Wont do for now
|
||||
[] List edit settings/object edit settings?
|
||||
[] dual engine support -- easily possible but has weird effects on win rate etc
|
||||
[] Currently, when saving an SGF after analyzing a game, Katrain stores data for every move by default. Like for dots, it would be great if it was possible to only store data for moves that we were interested in (e.g. only from this or that player, and/or only the most inefficient moves/most costly mistakes, etc.)
|
||||
[] Typing something in the comments window freezes Katrain -> probably keyboard shortcuts / solved by label
|
||||
[] Plus, you wouldn't need "B" and "W" beside these numbers if the top part of the window was black with the graph being white on top of it (and the bottom White with the graph being black as they are currently).
|
||||
|
||||
@@ -2,13 +2,13 @@ import heapq
|
||||
import math
|
||||
import random
|
||||
import time
|
||||
from typing import Dict, List, Tuple, Any
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
from common import OUTPUT_INFO, var_to_grid, OUTPUT_DEBUG, OUTPUT_ERROR
|
||||
from common import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_INFO, var_to_grid
|
||||
from engine import EngineDiedException
|
||||
from game import Move, Game, IllegalMoveException, GameNode
|
||||
from game import Game, GameNode, IllegalMoveException, Move
|
||||
|
||||
|
||||
def weighted_selection_without_replacement(items: List[Tuple[float, float, int, int]], pick_n: int) -> List[Tuple[float, float, int, int]]:
|
||||
@@ -71,10 +71,11 @@ def ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode
|
||||
elif "p:" in ai_mode:
|
||||
n_moves = int(ai_settings["pick_frac"] * len(legal_policy_moves) + ai_settings["pick_n"])
|
||||
if "influence" in ai_mode or "territory" in ai_mode:
|
||||
thr_line = ai_settings["threshold"] - 1 # zero-based
|
||||
if "influence" in ai_mode:
|
||||
weight = lambda x, y: (1 / ai_settings["line_weight"]) ** max(0, 3 - min(size[0] - 1 - x, x, y, size[1] - 1 - y))
|
||||
weight = lambda x, y: (1 / ai_settings["line_weight"]) ** (max(0, thr_line - min(size[0] - 1 - x, x)) + max(0, thr_line - min(size[1] - 1 - y, y)))
|
||||
else:
|
||||
weight = lambda x, y: (1 / ai_settings["line_weight"]) ** max(0, min(size[0] - 1 - x, x, y, size[1] - 1 - y) - 2)
|
||||
weight = lambda x, y: (1 / ai_settings["line_weight"]) ** (max(0, min(size[0] - 1 - x, x, size[1] - 1 - y, y) - thr_line))
|
||||
weighted_coords = [(policy_grid[y][x] * weight(x, y), weight(x, y), x, y) for x in range(size[0]) for y in range(size[1]) if policy_grid[y][x] > 0]
|
||||
ai_thoughts += f"Generated weights for {ai_mode} according to weight factor {ai_settings['line_weight']} and distance from 4th line. "
|
||||
elif "local" in ai_mode or "tenuki" in ai_mode:
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# This is a script that turns a KaTrain AI into a sort-of GTP compatible bot
|
||||
import json
|
||||
import time, sys
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from game import Game, Move
|
||||
|
||||
from ai import ai_move
|
||||
from engine import KataGoEngine, EngineDiedException
|
||||
from common import OUTPUT_ERROR, OUTPUT_INFO, OUTPUT_DEBUG, bot_strategy_names
|
||||
from common import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_INFO, bot_strategy_names
|
||||
from engine import EngineDiedException, KataGoEngine
|
||||
from game import Game, Move
|
||||
from sgf_parser import Move
|
||||
|
||||
DB_FILENAME = "ai_performance.pickle"
|
||||
@@ -14,6 +16,7 @@ if len(sys.argv) < 2:
|
||||
bot = "dev"
|
||||
else:
|
||||
bot = sys.argv[1].strip()
|
||||
port = int(sys.argv[2]) if len(sys.argv) > 2 else 8587
|
||||
|
||||
|
||||
class Logger:
|
||||
@@ -24,9 +27,10 @@ class Logger:
|
||||
|
||||
logger = Logger()
|
||||
|
||||
|
||||
ENGINE_SETTINGS = {
|
||||
# "katago": "../KataGo/cpp/katago",
|
||||
"katago": "python engine_connector.py 2222", # actual engine settings in engine_server.py
|
||||
"katago": f"python engine_connector.py {port}", # actual engine settings in engine_server.py
|
||||
"model": "models/b15-1.3.2.txt.gz",
|
||||
"config": "KataGo/analysis_config.cfg",
|
||||
"max_visits": 5,
|
||||
@@ -34,7 +38,7 @@ ENGINE_SETTINGS = {
|
||||
"enable_ownership": False,
|
||||
"threads": 1,
|
||||
}
|
||||
ai_settings = {"noise_strength": 0.8, "pick_n": 10, "pick_frac": 0.2, "stddev": 10, "line_weight": 10, "pick_override": 0.95}
|
||||
|
||||
|
||||
engine = KataGoEngine(logger, ENGINE_SETTINGS)
|
||||
|
||||
@@ -47,6 +51,9 @@ all_ai_settings["dev"] = all_ai_settings["P:Noise"]
|
||||
ai_strategy = bot_strategy_names[bot]
|
||||
ai_settings = all_ai_settings[ai_strategy]
|
||||
|
||||
print(f"starting bot {bot} using server port {port}", file=sys.stderr)
|
||||
print(ENGINE_SETTINGS, file=sys.stderr)
|
||||
print(ai_strategy, ai_settings, file=sys.stderr)
|
||||
|
||||
logger.log(f"STARTED ENGINE", OUTPUT_ERROR)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import List, Any, Tuple
|
||||
from typing import Any, List, Tuple
|
||||
|
||||
OUTPUT_ERROR = -1
|
||||
OUTPUT_INFO = 0
|
||||
@@ -26,3 +26,10 @@ def var_to_grid(array_var: List[Any], size: Tuple[int, int]) -> List[List[Any]]:
|
||||
grid[y] = array_var[ix : ix + size[0]]
|
||||
ix += size[0]
|
||||
return grid
|
||||
|
||||
|
||||
def evaluation_class(points_lost, eval_thresholds):
|
||||
i = 0
|
||||
while i < len(eval_thresholds) - 1 and points_lost < eval_thresholds[i]:
|
||||
i += 1
|
||||
return i
|
||||
+6
-2
@@ -99,16 +99,18 @@
|
||||
"pick_override": 0.95,
|
||||
"pick_n": 5,
|
||||
"pick_frac": 0.4,
|
||||
"threshold": 3.5,
|
||||
"line_weight": 10,
|
||||
"_help_right": "Samples `pick_n + pick_frac * <number of legal moves>` and plays the best one, biased towards the center.",
|
||||
"_help_right": "Samples `pick_n + pick_frac * <number of legal moves>` and plays the best one, biased to above the `threshold` line.",
|
||||
"_help_left": "Increase `line_weight` to penalize moves near the edge more."
|
||||
},
|
||||
"P:Territory": {
|
||||
"pick_override": 0.95,
|
||||
"pick_n": 5,
|
||||
"pick_frac": 0.4,
|
||||
"threshold": 3.5,
|
||||
"line_weight": 5,
|
||||
"_help_right": "Samples `pick_n + pick_frac * <number of legal moves>` and plays the best one, biased towards the edge.",
|
||||
"_help_right": "Samples `pick_n + pick_frac * <number of legal moves>` and plays the best one, biased to below the `threshold` line.",
|
||||
"_help_left": "Increase `line_weight` to penalize moves closer to the center more."
|
||||
}
|
||||
},
|
||||
@@ -116,6 +118,8 @@
|
||||
"starpoint_size": 0.1,
|
||||
"stone_size": 0.475,
|
||||
"eval_dot_max_size": 0.5,
|
||||
"eval_dot_min_size": 0.25,
|
||||
"visit_frac_small": 0.1,
|
||||
"stones": {
|
||||
"B": [
|
||||
0.05,
|
||||
|
||||
@@ -75,8 +75,8 @@ class KataGoEngine:
|
||||
continue
|
||||
analysis = json.loads(line)
|
||||
if analysis["id"] in self.queries:
|
||||
id = analysis["id"]
|
||||
callback, error_callback, start_time, next_move = self.queries[id]
|
||||
query_id = analysis["id"]
|
||||
callback, error_callback, start_time, next_move = self.queries[query_id]
|
||||
else:
|
||||
self.katrain.log(f"Query result {analysis['id']} discarded -- recent new game?", OUTPUT_DEBUG)
|
||||
continue
|
||||
@@ -87,12 +87,15 @@ class KataGoEngine:
|
||||
self.katrain.log(f"{analysis} received from KataGo", OUTPUT_ERROR)
|
||||
continue
|
||||
else:
|
||||
callback, error_callback, start_time, next_move = self.queries[id]
|
||||
callback, error_callback, start_time, next_move = self.queries[query_id]
|
||||
time_taken = time.time() - start_time
|
||||
self.katrain.log(f"[{time_taken:.1f}][{analysis['id']}] KataGo Analysis Received: {analysis.keys()}", OUTPUT_DEBUG)
|
||||
self.katrain.log(line, OUTPUT_EXTRA_DEBUG)
|
||||
del self.queries[query_id]
|
||||
try:
|
||||
callback(analysis)
|
||||
del self.queries[id]
|
||||
except Exception as e:
|
||||
self.katrain.log(f"Error in engine callback for query {query_id}: {e}", OUTPUT_ERROR)
|
||||
if getattr(self.katrain, "update_state", None): # easier mocking etc
|
||||
self.katrain.update_state()
|
||||
|
||||
|
||||
+6
-3
@@ -1,10 +1,13 @@
|
||||
# used to scale bots
|
||||
import socket, threading
|
||||
import traceback, sys, json
|
||||
import json
|
||||
import random
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
from common import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_INFO
|
||||
from engine import KataGoEngine
|
||||
from common import OUTPUT_ERROR, OUTPUT_INFO, OUTPUT_DEBUG
|
||||
|
||||
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8587
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import List, Union, Dict
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Union
|
||||
|
||||
from common import var_to_grid
|
||||
from engine import KataGoEngine
|
||||
@@ -206,8 +206,12 @@ class Game:
|
||||
game_name = f"katrain_{black} vs {white} {self.game_id}"
|
||||
file_name = os.path.join(path, f"{game_name}.sgf")
|
||||
os.makedirs(os.path.dirname(file_name), exist_ok=True)
|
||||
|
||||
show_dots_for = {p: self.katrain.config("trainer/eval_show_ai") or "ai" not in self.katrain.controls.player_mode(p) for p in Move.PLAYERS}
|
||||
thresholds = self.katrain.config("trainer/eval_thresholds")
|
||||
sgf = self.root.sgf(save_comments_player=show_dots_for, save_comments_class=self.katrain.config("sgf/save_feedback"), eval_thresholds=thresholds)
|
||||
with open(file_name, "w") as f:
|
||||
f.write(self.root.sgf())
|
||||
f.write(sgf)
|
||||
return f"SGF with analysis written to {file_name}"
|
||||
|
||||
def analyze_extra(self, mode):
|
||||
|
||||
+8
-4
@@ -2,7 +2,7 @@ import copy
|
||||
import random
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from common import var_to_grid
|
||||
from common import evaluation_class, var_to_grid
|
||||
from sgf_parser import Move, SGFNode
|
||||
|
||||
|
||||
@@ -19,10 +19,14 @@ class GameNode(SGFNode):
|
||||
self.move_number = 0
|
||||
self.undo_threshold = random.random() # for fractional undos, store the random threshold in the move itself for consistency
|
||||
|
||||
@property
|
||||
def sgf_properties(self):
|
||||
def sgf_properties(self, save_comments_player, save_comments_player_class, eval_thresholds):
|
||||
properties = copy.copy(super().sgf_properties())
|
||||
if self.points_lost:
|
||||
show_class = save_comments_player_class[evaluation_class(self.points_lost, eval_thresholds)]
|
||||
else:
|
||||
show_class = False
|
||||
if save_comments_player.get(self.player, False) and show_class:
|
||||
best_sq = []
|
||||
properties = copy.copy(super().sgf_properties)
|
||||
if best_sq and "SQ" not in properties:
|
||||
properties["SQ"] = best_sq
|
||||
comment = self.comment(sgf=True)
|
||||
|
||||
+13
-15
@@ -6,7 +6,7 @@ from kivy.properties import ListProperty
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
from common import OUTPUT_DEBUG
|
||||
from common import OUTPUT_DEBUG, evaluation_class
|
||||
from game import Move
|
||||
from gui.kivyutils import draw_circle, draw_text
|
||||
from common import var_to_grid
|
||||
@@ -92,7 +92,7 @@ class BadukPanWidget(Widget):
|
||||
self.draw_hover_contents() # remove ghost
|
||||
|
||||
# drawing functions
|
||||
def on_size(self, *args):
|
||||
def on_size(self, *_args):
|
||||
self.draw_board()
|
||||
self.draw_board_contents()
|
||||
|
||||
@@ -112,13 +112,11 @@ class BadukPanWidget(Widget):
|
||||
|
||||
def eval_color(self, points_lost):
|
||||
colors = self.ui_config["eval_colors"]
|
||||
thresholds = self.trainer_config["eval_thresholds"]
|
||||
i = 0
|
||||
while i < len(thresholds) and points_lost < thresholds[i]:
|
||||
i += 1
|
||||
return colors[min(i, len(colors) - 1)]
|
||||
i = evaluation_class(points_lost, self.trainer_config["eval_thresholds"])
|
||||
print(i, colors)
|
||||
return colors[i]
|
||||
|
||||
def draw_board(self, *args):
|
||||
def draw_board(self, *_args):
|
||||
if not self.ui_config:
|
||||
return
|
||||
katrain = self.katrain
|
||||
@@ -167,7 +165,7 @@ class BadukPanWidget(Widget):
|
||||
for i in range(board_size_y):
|
||||
draw_text(pos=(self.gridpos_x[0] - coord_offset, self.gridpos_y[i]), text=str(i + 1), font_size=self.grid_size / 1.5)
|
||||
|
||||
def draw_board_contents(self, *args):
|
||||
def draw_board_contents(self, *_args):
|
||||
if not self.ui_config:
|
||||
return
|
||||
stone_color = self.ui_config["stones"]
|
||||
@@ -194,7 +192,7 @@ class BadukPanWidget(Widget):
|
||||
evalsize = 1
|
||||
for m in node.move_with_placements:
|
||||
if has_stone.get(m.coords) and not drawn_stone.get(m.coords): # skip captures, last only for
|
||||
move_eval_on = full_eval_on or (i < show_n_eval and show_dots_for.get(m.player))
|
||||
move_eval_on = show_dots_for.get(m.player) and (i < show_n_eval or full_eval_on)
|
||||
if move_eval_on and points_lost is not None:
|
||||
evalcol = self.eval_color(points_lost)
|
||||
else:
|
||||
@@ -212,7 +210,7 @@ class BadukPanWidget(Widget):
|
||||
self.draw_stone(6, c, stone_color["B"], outline_color["B"], stone_color["W"], evalcol, evalsize)
|
||||
self.draw_stone(7, c, stone_color["W"], outline_color["W"], None, evalcol, evalsize)
|
||||
self.draw_stone(8, c, stone_color["W"], outline_color["W"], stone_color["B"], evalcol, evalsize)
|
||||
self.draw_stone(9, c, [*evalcol, 0.5], scale=0.8)
|
||||
self.draw_stone(9, c, [*evalcol[:3], 0.5], scale=0.8)
|
||||
|
||||
# ownership - allow one move out of date for smooth animation
|
||||
ownership = current_node.ownership or (current_node.parent and current_node.parent.ownership)
|
||||
@@ -234,7 +232,7 @@ class BadukPanWidget(Widget):
|
||||
pass_btn = katrain.board_controls.pass_btn
|
||||
pass_btn.canvas.after.clear()
|
||||
if katrain.controls.policy.active and policy:
|
||||
policy_grid = var_to_grid(policy, [board_size_x, board_size_y])
|
||||
policy_grid = var_to_grid(policy, (board_size_x, board_size_y))
|
||||
best_move_policy = max(*policy)
|
||||
for y in range(board_size_y - 1, -1, -1):
|
||||
for x in range(board_size_x):
|
||||
@@ -278,7 +276,7 @@ class BadukPanWidget(Widget):
|
||||
|
||||
self.draw_hover_contents()
|
||||
|
||||
def draw_hover_contents(self, *args):
|
||||
def draw_hover_contents(self, *_args):
|
||||
ghost_alpha = self.ui_config["ghost_alpha"]
|
||||
katrain = self.katrain
|
||||
game_ended = katrain.game.ended
|
||||
@@ -299,7 +297,7 @@ class BadukPanWidget(Widget):
|
||||
alpha, scale = self.ui_config["ghost_alpha"], 1.0
|
||||
if i == 0:
|
||||
alpha += self.ui_config["top_move_x_alpha"]
|
||||
elif d["visits"] < 0.1 * hint_moves[0]["visits"]: # TODO: config?
|
||||
elif d["visits"] < self.ui_config["visit_frac_small"] * hint_moves[0]["visits"]:
|
||||
scale = 0.8
|
||||
self.active_hints.append(move.coords)
|
||||
|
||||
@@ -322,7 +320,7 @@ class BadukPanWidget(Widget):
|
||||
Color(*stone_color[opp_player])
|
||||
draw_text(pos=board_coords, text=str(i + 1), font_size=sizefac * self.grid_size / 1.45)
|
||||
elif not self.show_pv_for:
|
||||
self.draw_stone(move.coords[0], move.coords[1], [*self.eval_color(d["pointsLost"]), alpha], scale=scale)
|
||||
self.draw_stone(move.coords[0], move.coords[1], [*self.eval_color(d["pointsLost"])[:3], alpha], scale=scale)
|
||||
|
||||
# hover next move ghost stone
|
||||
if self.ghost_stone:
|
||||
|
||||
+5
-7
@@ -1,5 +1,3 @@
|
||||
from kivy.graphics.context_instructions import Color
|
||||
from kivy.graphics.vertex_instructions import Line, SmoothLine
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.popup import Popup
|
||||
|
||||
@@ -15,7 +13,7 @@ class Controls(BoxLayout):
|
||||
def set_status(self, msg, at_node=None):
|
||||
self.status = msg
|
||||
self.status_node = at_node or self.katrain.game and self.katrain.game.current_node
|
||||
self.info.text = msg
|
||||
self.status_label.text = msg
|
||||
self.update_evaluation()
|
||||
|
||||
def select_mode(self, mode):
|
||||
@@ -44,12 +42,12 @@ class Controls(BoxLayout):
|
||||
katrain = self.katrain
|
||||
current_node = katrain.game and katrain.game.current_node
|
||||
|
||||
info = ""
|
||||
if current_node is self.status_node or (self.status is not None and self.status_node is None and current_node.is_root): # startup errors on root
|
||||
info += self.status + "\n"
|
||||
else:
|
||||
if current_node is not self.status_node and not (self.status is not None and self.status_node is None and current_node.is_root): # startup errors on root
|
||||
self.status_label.text = ""
|
||||
self.status_node = None
|
||||
|
||||
info = ""
|
||||
|
||||
if current_node:
|
||||
move = current_node.single_move
|
||||
next_player_is_human_or_both_robots = current_node.player and ("ai" not in self.player_mode(current_node.player) or "ai" in self.player_mode(current_node.next_player))
|
||||
|
||||
@@ -13,6 +13,7 @@ from kivy.uix.button import Button
|
||||
from kivy.uix.checkbox import CheckBox
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.scrollview import ScrollView
|
||||
from kivy.uix.spinner import Spinner
|
||||
from kivy.uix.textinput import TextInput
|
||||
from kivy.uix.widget import Widget
|
||||
@@ -38,6 +39,11 @@ class BackgroundLabel(Label, BackgroundColor):
|
||||
pass
|
||||
|
||||
|
||||
class ScrollableLabel(ScrollView):
|
||||
text = StringProperty("")
|
||||
border_color = ListProperty([0, 0, 0, 1])
|
||||
|
||||
|
||||
class StyledButton(Button):
|
||||
button_color = ListProperty([])
|
||||
button_color_down = ListProperty([])
|
||||
|
||||
+10
-5
@@ -1,9 +1,11 @@
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, DefaultDict
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.popup import Popup
|
||||
|
||||
from common import OUTPUT_DEBUG, OUTPUT_ERROR
|
||||
from engine import KataGoEngine
|
||||
@@ -27,7 +29,7 @@ class InputParseError(Exception):
|
||||
|
||||
|
||||
class QuickConfigGui(BoxLayout):
|
||||
def __init__(self, katrain, popup, initial_values=None, **kwargs):
|
||||
def __init__(self, katrain: "KaTrainGui", popup: Popup, initial_values: Dict = None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.katrain = katrain
|
||||
self.popup = popup
|
||||
@@ -74,7 +76,7 @@ class LoadSGFPopup(BoxLayout):
|
||||
|
||||
|
||||
class NewGamePopup(QuickConfigGui):
|
||||
def __init__(self, katrain, popup, properties, **kwargs):
|
||||
def __init__(self, katrain: "KaTrainGui", popup: Popup, properties: Dict, **kwargs):
|
||||
properties["RU"] = KataGoEngine.get_rules(katrain.game.root)
|
||||
super().__init__(katrain, popup, properties, **kwargs)
|
||||
self.rules_spinner.values = list(set(self.katrain.engine.RULESETS.values()))
|
||||
@@ -93,7 +95,7 @@ class NewGamePopup(QuickConfigGui):
|
||||
|
||||
|
||||
class ConfigPopup(QuickConfigGui):
|
||||
def __init__(self, katrain, popup, config, ignore_cats, **kwargs):
|
||||
def __init__(self, katrain: "KaTrainGui", popup: Popup, config: Dict, ignore_cats: List, **kwargs):
|
||||
self.config = config
|
||||
self.ignore_cats = ignore_cats
|
||||
self.orientation = "vertical"
|
||||
@@ -134,7 +136,7 @@ class ConfigPopup(QuickConfigGui):
|
||||
self.add_widget(btn_container)
|
||||
|
||||
def update_config(self, save_to_file=False):
|
||||
updated_cat = defaultdict(list)
|
||||
updated_cat = defaultdict(list) # type: DefaultDict[List[str]]
|
||||
try:
|
||||
for k, v in self.collect_properties(self).items():
|
||||
k1, k2 = k.split("/")
|
||||
@@ -157,7 +159,9 @@ class ConfigPopup(QuickConfigGui):
|
||||
if {key for key in engine_updates if key not in {"max_visits", "max_time", "enable_ownership"}}:
|
||||
self.katrain.log(f"Restarting Engine after {engine_updates} settings change")
|
||||
self.katrain.controls.set_status(f"Restarting Engine after {engine_updates} settings change")
|
||||
old_engine = self.katrain.engine
|
||||
|
||||
def restart_engine(_dt):
|
||||
old_engine = self.katrain.engine # type: KataGoEngine
|
||||
new_engine = KataGoEngine(self.katrain, self.config["engine"])
|
||||
self.katrain.engine = {"B": new_engine, "W": new_engine}
|
||||
self.katrain.game.engine = new_engine
|
||||
@@ -165,6 +169,7 @@ class ConfigPopup(QuickConfigGui):
|
||||
old_engine.shutdown(finish=True)
|
||||
else:
|
||||
self.katrain.game.analyze_all_nodes() # old engine was broken, so make sure we redo any failures
|
||||
Clock.schedule_once(restart_engine, 0)
|
||||
|
||||
self.katrain.debug_level = self.config["debug"]["level"]
|
||||
self.katrain.update_state(redraw_board=True)
|
||||
|
||||
+39
-4
@@ -360,11 +360,37 @@
|
||||
Label:
|
||||
size_hint: 0.025, 1
|
||||
|
||||
<ScrollableLabel>:
|
||||
do_scroll_x: False
|
||||
scroll_type: ['bars']
|
||||
bar_width: 5
|
||||
bar_color: BUTTON_COLOR
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: root.border_color
|
||||
Line:
|
||||
rectangle: [self.pos[0],self.pos[1]+1,self.width-1,self.height-2]
|
||||
width:1
|
||||
Color:
|
||||
rgba: [*[(c+1)/2 for c in root.border_color[:3]], root.border_color[3]]
|
||||
Line:
|
||||
rectangle: [self.pos[0]+1,self.pos[1]+2,self.width-3,self.height-4]
|
||||
width:1
|
||||
Label:
|
||||
padding: 5, 5
|
||||
font_size: dp(20)
|
||||
color: BLACK
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
text_size: self.width, None
|
||||
text: root.text
|
||||
|
||||
<Controls>:
|
||||
orientation: 'vertical'
|
||||
play_tab_button: play_tab_button
|
||||
analyze_tab_button: analyze_tab_button
|
||||
info: info
|
||||
status_label: status_label
|
||||
win_rate: win_rate
|
||||
score_change: score_change
|
||||
score: score
|
||||
@@ -509,7 +535,7 @@
|
||||
StyledSpinner:
|
||||
id: B_AI_mode
|
||||
values: ['Default']
|
||||
sync_height_frac: 0.66
|
||||
sync_height_frac: 0.7
|
||||
size_hint: 0.3, 1
|
||||
on_text: if B_player_mode.children: B_player_mode.children[0].trigger_action(duration=0)
|
||||
Label:
|
||||
@@ -527,7 +553,7 @@
|
||||
StyledSpinner:
|
||||
id: W_AI_mode
|
||||
size_hint: 0.3, 1
|
||||
sync_height_frac: 0.66
|
||||
sync_height_frac: 0.7
|
||||
values: ['Default']
|
||||
on_text: if W_player_mode.children: W_player_mode.children[0].trigger_action(duration=0)
|
||||
BoxLayout:
|
||||
@@ -549,9 +575,18 @@
|
||||
size_hint: 1, 0.0225
|
||||
label: 'Score change'
|
||||
text: ''
|
||||
TextInput:
|
||||
BoxLayout:
|
||||
size_hint: 1,0.2
|
||||
orientation: 'vertical'
|
||||
ScrollableLabel:
|
||||
id: status_label
|
||||
border_color: 0.5,0.1,0.1,1
|
||||
opacity: 1 if self.text else 0
|
||||
text: ''
|
||||
size_hint_y: 0.00001 if not self.text else 0.66
|
||||
ScrollableLabel:
|
||||
id: info
|
||||
size_hint: 1, 0.2
|
||||
size_hint: 1, 1 - status_label.size_hint_y
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
size_hint: 1, None
|
||||
|
||||
+3
-5
@@ -1,5 +1,3 @@
|
||||
import inspect
|
||||
|
||||
from kivy.config import Config # isort:skip
|
||||
|
||||
Config.set("input", "mouse", "mouse,multitouch_on_demand") # isort:skip # no red dots on right click
|
||||
@@ -10,13 +8,13 @@ import sys
|
||||
import threading
|
||||
import traceback
|
||||
from queue import Queue
|
||||
from typing import Optional
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.core.clipboard import Clipboard
|
||||
from kivy.core.window import Window
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
from kivy.uix.popup import Popup
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
from ai import ai_move
|
||||
from common import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_INFO
|
||||
@@ -33,14 +31,14 @@ class KaTrainGui(BoxLayout):
|
||||
def __init__(self, **kwargs):
|
||||
super(KaTrainGui, self).__init__(**kwargs)
|
||||
self.debug_level = 0
|
||||
self.engine = None
|
||||
self.engine = None # type: Optional[KataGoEngine]
|
||||
self.game = None
|
||||
self.logger = lambda message, level=OUTPUT_INFO: self.log(message, level)
|
||||
|
||||
self._load_config()
|
||||
|
||||
self.debug_level = self.config("debug/level", OUTPUT_INFO)
|
||||
self.controls.ai_mode_groups["W"].values = self.controls.ai_mode_groups["B"].values = list(self.config("ai").keys()) + ["<Pause>"]
|
||||
self.controls.ai_mode_groups["W"].values = self.controls.ai_mode_groups["B"].values = list(self.config("ai").keys())
|
||||
self.message_queue = Queue()
|
||||
|
||||
self._keyboard = Window.request_keyboard(None, self, "")
|
||||
|
||||
+6
-5
@@ -1,16 +1,17 @@
|
||||
# This is a script I use to test the performance of AIs
|
||||
import pickle
|
||||
import sys
|
||||
import threading
|
||||
import time, sys
|
||||
import time
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
import pickle
|
||||
from concurrent.futures.thread import ThreadPoolExecutor
|
||||
|
||||
from game import Game
|
||||
from ai import ai_move
|
||||
from engine import KataGoEngine
|
||||
from common import OUTPUT_ERROR, OUTPUT_INFO, OUTPUT_DEBUG
|
||||
from common import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_INFO
|
||||
from elote import EloCompetitor
|
||||
from engine import KataGoEngine
|
||||
from game import Game
|
||||
|
||||
DB_FILENAME = "ai_performance.pickle"
|
||||
|
||||
|
||||
+4
-4
@@ -71,16 +71,16 @@ class SGFNode:
|
||||
if parent and move:
|
||||
self.set_property(move.player, move.sgf(self.board_size))
|
||||
|
||||
@property
|
||||
def sgf_properties(self) -> Dict:
|
||||
def sgf_properties(self, **xargs) -> Dict:
|
||||
"""For hooking into in a subclass and overriding/formatting any additional properties to be output"""
|
||||
return copy.deepcopy(self.properties)
|
||||
|
||||
def sgf(self) -> str:
|
||||
def sgf(self, **xargs) -> str:
|
||||
"""Generates an SGF, calling sgf_properties on each node with the given xargs, so it can filter relevant properties if needed."""
|
||||
import sys
|
||||
|
||||
sys.setrecursionlimit(max(sys.getrecursionlimit(), 3 * 29 * 29)) # thanks to lightvector for causing stack overflows
|
||||
sgf_str = "".join([prop + "".join(f"[{v}]" for v in values) for prop, values in self.sgf_properties.items() if values])
|
||||
sgf_str = "".join([prop + "".join(f"[{v}]" for v in values) for prop, values in self.sgf_properties(**xargs).items() if values])
|
||||
if self.children:
|
||||
children = [c.sgf() for c in self.children]
|
||||
if len(children) == 1:
|
||||
|
||||
+8
-3
@@ -8,7 +8,10 @@ if len(sys.argv) < 2:
|
||||
exit(0)
|
||||
|
||||
bot = sys.argv[1].strip()
|
||||
port = int(sys.argv[2]) if len(sys.argv) > 2 else 8587
|
||||
|
||||
username = f"katrain-{bot}"
|
||||
|
||||
greetings = {
|
||||
"dev": "Experimental!",
|
||||
"strong": "Play top policy move.",
|
||||
@@ -40,6 +43,8 @@ GREETING = (
|
||||
)
|
||||
BYEMSG = "Thank you for playing. If you have any feedback, please message my admin!"
|
||||
MAXGAMES = 10
|
||||
os.system(
|
||||
f'gtp2ogs --apikey {APIKEY} --username {username} --greeting "{GREETING}" --rankedonly --farewell "{BYEMSG}" --ogspv katago --noclock --speeds blitz,live --maxconnectedgames {MAXGAMES} --persist --minrank 20k --noautohandicap --maxhandicap 0 --boardsizes 9,13,19 --komis automatic,6.5 -- python ai2gtp.py {bot}'
|
||||
)
|
||||
# --rankedonly
|
||||
|
||||
cmd = f'gtp2ogs --apikey {APIKEY} --username {username} --greeting "{GREETING}" --farewell "{BYEMSG}" --ogspv katago --noclock --nopause --speeds blitz,live --maxconnectedgames {MAXGAMES} --persist --minrank 20k --noautohandicap --maxhandicap 0 --boardsizes 19 --komis automatic,6.5 -- python ai2gtp.py {bot} {port}'
|
||||
print(f"starting bot {username} using server port {port} --> {cmd}")
|
||||
os.system(cmd)
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
screen echo 1
|
||||
split -v
|
||||
focus
|
||||
screen echo 2
|
||||
split
|
||||
focus
|
||||
screen echo 3
|
||||
focus
|
||||
split
|
||||
focus
|
||||
screen echo 4
|
||||
Reference in new issue
Block a user