count, cache
This commit is contained in:
1 parent
2dc47620a7
commit
4b08031c7f
10 files changed
+80
-24
No files matched your search
+2
-2
@@ -71,9 +71,9 @@ def malkovich_analysis(cn):
|
||||
if cn.analysis_ready and cn.parent and cn.parent.analysis_ready:
|
||||
dscore = cn.analysis["root"]["scoreLead"] - cn.parent.analysis["root"]["scoreLead"]
|
||||
logger.log(f"dscore {dscore} = {cn.analysis['root']['scoreLead']} {cn.parent.analysis['root']['scoreLead']} at {move}...", OUTPUT_ERROR)
|
||||
if abs(dscore) > REPORT_SCORE_THRESHOLD:
|
||||
if abs(dscore) > REPORT_SCORE_THRESHOLD and (cn.player == "B" and dscore < 0 or cn.player == "W" and dscore > 0): # relevant mistakes
|
||||
favpl = "B" if dscore > 0 else "W"
|
||||
msg = f"MALKOVICH:{cn.player} {cn.single_move.gtp()} caused a significant score change {favpl}{abs(dscore):+.1f} -> Winrate {cn.format_win_rate()} ScoreLead {cn.format_score()}"
|
||||
msg = f"MALKOVICH:{cn.player} {cn.single_move.gtp()} caused a significant score change ({favpl} gained {abs(dscore):.1f} points) -> Win Rate {cn.format_win_rate()} Expected Score {cn.format_score()}"
|
||||
if cn.ai_thoughts:
|
||||
msg += f" AI Thoughts: {cn.ai_thoughts}"
|
||||
print(msg, file=sys.stderr)
|
||||
|
||||
+4
-2
@@ -32,8 +32,10 @@ settings_dump = ", ".join(f"{k}={v}" for k, v in ai_settings.items() if not k.st
|
||||
print(settings_dump)
|
||||
GREETING = f"Hello, welcome to an experimental version of KaTrain AIs - These are based on weakened policy nets of KataGo. Current mode is: {greetings[bot]}"
|
||||
if settings:
|
||||
GREETING += "Settings: {settings_dump}."
|
||||
BYEMSG = "Thank you for playing. If you have any feedback, please message my admin!"
|
||||
GREETING += f"Settings: {settings_dump}."
|
||||
BYEMSG = (
|
||||
"Thank you for playing. If you have any feedback, please message my admin! Please note that score estimates in the malkovich log are based on low visits and likely inaccurate."
|
||||
)
|
||||
|
||||
cmd = f'gtp2ogs --debug --apikey {APIKEY} --username {username} --greeting "{GREETING}" --farewell "{BYEMSG}" {BOT_SETTINGS} --aichat --noclock --nopause --speeds blitz,live --persist --minrank 25k --komis automatic,6.5,7.5 -- python bots/ai2gtp.py {bot} {port}'
|
||||
print(f"starting bot {username} using server port {port} --> {cmd}")
|
||||
|
||||
@@ -36,11 +36,13 @@ class KataGoEngine:
|
||||
self.katago_process = None
|
||||
self.base_priority = 0
|
||||
self._lock = threading.Lock()
|
||||
self.start()
|
||||
self.analysis_thread = threading.Thread(target=self._analysis_read_thread, daemon=True).start()
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
self.katrain.log(f"Starting KataGo with {self.command}", OUTPUT_DEBUG)
|
||||
self.katago_process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
self.analysis_thread = threading.Thread(target=self._analysis_read_thread, daemon=True).start()
|
||||
except FileNotFoundError:
|
||||
self.katrain.log(
|
||||
f"Starting kata with command '{self.command}' failed. If you are on Mac or Linux, please change the settings or configuration file (config.json) to point to the correct KataGo executable.",
|
||||
@@ -51,6 +53,11 @@ class KataGoEngine:
|
||||
self.base_priority += 1
|
||||
self.queries = {}
|
||||
|
||||
def restart(self):
|
||||
self.queries = {}
|
||||
self.shutdown(finish=False)
|
||||
self.start()
|
||||
|
||||
def shutdown(self, finish=False):
|
||||
process = self.katago_process
|
||||
if finish and process:
|
||||
|
||||
@@ -5,7 +5,7 @@ import threading
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Union
|
||||
|
||||
from common import var_to_grid
|
||||
from common import var_to_grid, OUTPUT_INFO, OUTPUT_ERROR, OUTPUT_DEBUG
|
||||
from engine import KataGoEngine
|
||||
from game_node import GameNode
|
||||
from sgf_parser import SGF, Move
|
||||
@@ -193,8 +193,43 @@ class Game:
|
||||
return self.current_node.parent and self.current_node.is_pass and self.current_node.parent.is_pass
|
||||
|
||||
@property
|
||||
def prisoner_count(self):
|
||||
return [sum([m.player == player for m in self.prisoners]) for player in Move.PLAYERS]
|
||||
def prisoner_count(self) -> Dict: # returns prisoners that are of a certain colour as {B: black stones captures, W: white stones captures}
|
||||
return {player: sum([m.player == player for m in self.prisoners]) for player in Move.PLAYERS}
|
||||
|
||||
@property
|
||||
def manual_score(self):
|
||||
rules = self.engines["B"].get_rules(self.root)
|
||||
if not self.current_node.ownership or rules != "japanese":
|
||||
if not self.current_node.score:
|
||||
return None
|
||||
self.katrain.log(f"rules '{rules}' are not japanese, or no ownership available ({not self.current_node.ownership}) -> no manual score available", OUTPUT_DEBUG)
|
||||
return self.current_node.format_score(round(2 * self.current_node.score) / 2) + "?"
|
||||
board_size_x, board_size_y = self.board_size
|
||||
ownership_grid = var_to_grid(self.current_node.ownership, (board_size_x, board_size_y))
|
||||
stones = {m.coords: m.player for m in self.stones}
|
||||
lo_threshold = 0.15
|
||||
hi_threshold = 0.85
|
||||
max_unknown = 10
|
||||
max_dame = 4*(board_size_x+board_size_y)
|
||||
def japanese_score_square(square, owner):
|
||||
player = stones.get(square, None)
|
||||
if (player == "B" and owner > hi_threshold) or (player == "W" and owner < -hi_threshold) or abs(owner) < lo_threshold:
|
||||
return 0 # dame or own stones
|
||||
if player is None and abs(owner) >= hi_threshold:
|
||||
return round(owner) # surrounded empty intersection
|
||||
if (player == "B" and owner < -hi_threshold) or (player == "W" and owner > hi_threshold):
|
||||
return 2 * round(owner) # captured stone
|
||||
return math.nan # unknown!
|
||||
|
||||
scored_squares = [japanese_score_square((x, y), ownership_grid[y][x]) for y in range(board_size_y) for x in range(board_size_x)]
|
||||
num_sq = {t: sum([s == t for s in scored_squares]) for t in [-2, -1, 0, 1, 2]}
|
||||
num_unkn = sum(math.isnan(s) for s in scored_squares)
|
||||
prisoners = self.prisoner_count
|
||||
score = sum([t * n for t, n in num_sq.items()]) + prisoners["W"] - prisoners["B"] - self.komi
|
||||
self.katrain.log(f"Manual Scoring: {num_sq} score by square with {num_unkn} unknown, {prisoners} captures, and {self.komi} komi -> score = {score}", OUTPUT_INFO)
|
||||
if num_unkn > max_unknown or num_sq[0] > max_dame:
|
||||
return None
|
||||
return self.current_node.format_score(score)
|
||||
|
||||
def __repr__(self):
|
||||
return "\n".join("".join(Move.PLAYERS[self.chains[c][0].player] if c >= 0 else "-" for c in line) for line in self.board) + f"\ncaptures: {self.prisoner_count}"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import copy
|
||||
import math
|
||||
import random
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
|
||||
+7
-8
@@ -223,15 +223,14 @@ class BadukPanWidget(Widget):
|
||||
# ownership - allow one move out of date for smooth animation
|
||||
ownership = current_node.ownership or (current_node.parent and current_node.parent.ownership)
|
||||
if katrain.controls.ownership.active and ownership:
|
||||
ownership_grid = var_to_grid(ownership, (board_size_x, board_size_y))
|
||||
rsz = self.grid_size * 0.2
|
||||
ix = 0
|
||||
for y in range(board_size_y - 1, -1, -1):
|
||||
for x in range(board_size_x):
|
||||
ix_owner = "B" if ownership[ix] > 0 else "W"
|
||||
ix_owner = "B" if ownership_grid[y][x] > 0 else "W"
|
||||
if ix_owner != (has_stone.get((x, y), -1)):
|
||||
Color(*stone_color[ix_owner], abs(ownership[ix]))
|
||||
Color(*stone_color[ix_owner], abs(ownership_grid[y][x]))
|
||||
Rectangle(pos=(self.gridpos_x[x] - rsz / 2, self.gridpos_y[y] - rsz / 2), size=(rsz, rsz))
|
||||
ix = ix + 1
|
||||
|
||||
policy = current_node.policy
|
||||
if not policy and current_node.parent and current_node.parent.policy and "ai" in katrain.controls.player_mode("B") and "ai" in katrain.controls.player_mode("W"):
|
||||
@@ -276,14 +275,14 @@ class BadukPanWidget(Widget):
|
||||
passed = len(nodes) > 1 and current_node.is_pass
|
||||
if passed:
|
||||
if game_ended:
|
||||
text = "game\nend"
|
||||
text = katrain.game.manual_score or 'game\nend'
|
||||
else:
|
||||
text = "pass"
|
||||
Color(0.45, 0.05, 0.45, 0.5)
|
||||
center = (self.pos[0] + self.width / 2, self.pos[1] + self.height / 2)
|
||||
Color(0.45, 0.05, 0.45, 0.7)
|
||||
center = (self.gridpos_x[int(board_size_x/2)], self.gridpos_y[int(board_size_y/2)])
|
||||
size = min(self.width, self.height) * 0.22
|
||||
Ellipse(pos=(center[0] - size / 2, center[1] - size / 2), size=(size, size))
|
||||
Color(0.15, 0.15, 0.15)
|
||||
Color(0.85, 0.85, 0.85)
|
||||
draw_text(pos=center, text=text, font_size=size * 0.25, halign="center", outline_color=[0.95, 0.95, 0.95])
|
||||
|
||||
self.draw_hover_contents()
|
||||
|
||||
+2
-1
@@ -89,13 +89,14 @@ class ToggleButtonContainer(GridLayout):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.rows = 1
|
||||
self.cols = len(self.options)
|
||||
Clock.schedule_once(self._build, 0)
|
||||
|
||||
def on_selection(self, *args):
|
||||
pass
|
||||
|
||||
def _build(self, _dt):
|
||||
self.rows = 1
|
||||
self.cols = len(self.options)
|
||||
self.group = self.group or str(random.random())
|
||||
if not self.selected and self.options:
|
||||
|
||||
@@ -92,6 +92,9 @@ class NewGamePopup(QuickConfigGui):
|
||||
if x > 52 or y > 52:
|
||||
self.info.text = "Board size too big, should be at most 52"
|
||||
return
|
||||
if self.restart.active:
|
||||
self.katrain.log("Restarting Engine")
|
||||
self.katrain.engine.restart()
|
||||
self.katrain("new-game", new_root)
|
||||
self.popup.dismiss()
|
||||
|
||||
|
||||
+12
-4
@@ -702,9 +702,10 @@
|
||||
orientation: 'vertical'
|
||||
rules_spinner: rules_spinner
|
||||
info: info
|
||||
restart: restart
|
||||
GridLayout:
|
||||
size_hint: 1, 0.8
|
||||
rows: 5
|
||||
rows: 6
|
||||
cols: 3
|
||||
spacing: 2
|
||||
padding: 2
|
||||
@@ -757,13 +758,23 @@
|
||||
input_property: 'RU'
|
||||
id: rules_spinner
|
||||
Label:
|
||||
ScaledLightLabel:
|
||||
text: 'Clear cache'
|
||||
CheckBox:
|
||||
color: WHITE
|
||||
id: restart
|
||||
ScaledLightLabel:
|
||||
font_size: 0.35*self.size[1]
|
||||
text: 'avoids replaying\nidentical games'
|
||||
LightHelpLabel:
|
||||
text: "Use x:y (e.g. 19:9) to play on a non-square board."
|
||||
size_hint: 1,2
|
||||
text_size: self.width-6, None
|
||||
font_size: self.size[1]/6
|
||||
id: info
|
||||
LightHelpLabel:
|
||||
text: "Note that handicaps above 9 are not supported on non-square boards."
|
||||
font_size: self.size[1]/6
|
||||
text_size: self.width-6, None
|
||||
id: info
|
||||
StyledButton:
|
||||
@@ -771,6 +782,3 @@
|
||||
text: 'Start\nGame'
|
||||
font_size: 0.3 * self.size[1]
|
||||
on_press: root.new_game()
|
||||
|
||||
#EffectWidget: effects: [ew.FXAAEffect()]
|
||||
|
||||
+3
-3
@@ -104,8 +104,8 @@ class KaTrainGui(BoxLayout):
|
||||
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.board_controls.black_prisoners.text = str(prisoners[1])
|
||||
self.board_controls.white_prisoners.text = str(prisoners[0])
|
||||
self.board_controls.black_prisoners.text = str(prisoners["W"])
|
||||
self.board_controls.white_prisoners.text = str(prisoners["B"])
|
||||
|
||||
# update engine status dot
|
||||
if not self.engine or not self.engine.katago_process or self.engine.katago_process.poll() is not None:
|
||||
@@ -190,7 +190,7 @@ class KaTrainGui(BoxLayout):
|
||||
move_tree = KaTrainSGF.parse_file(files[0])
|
||||
except ParseError as e:
|
||||
self.log(f"Failed to load SGF. Parse Error: {e}", OUTPUT_ERROR)
|
||||
return
|
||||
return
|
||||
self._do_new_game(move_tree=move_tree, analyze_fast=popup_contents.fast.active)
|
||||
|
||||
popup_contents.filesel.on_submit = readfile
|
||||
|
||||
Reference in new issue
Block a user