stuff
This commit is contained in:
1 parent
81304ba3a3
commit
1a3e21610b
12 files changed
+217
-104
No files matched your search
@@ -3,26 +3,45 @@
|
||||
[x] remove fast -> in settings?
|
||||
[x] Polish graph
|
||||
[x] BoxLayout padding/spacing use instead of fiddling?
|
||||
[x] P+noise pass not noisy
|
||||
[x] graph wonky on branch switch -> check in children &c.
|
||||
[x] Scrolling add a move on the board instead of navigating through the game. This was already the case in the 0.9 version and it's quite annoying as scrolling seemed only natural and I kept forgetting not to do it :p
|
||||
|
||||
[] Score instead of game end
|
||||
[] engine status
|
||||
[] README
|
||||
[] Release notes
|
||||
[] more AI modes?
|
||||
[] P+noise pass not noisy
|
||||
[] show PV on hint hover? Although Katrain wasn't meant to be like Lizzie to begin with, it would be really neat if we could visualize the expected variations when hovering over the top moves.
|
||||
[] 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
|
||||
|
||||
|
||||
- dots: SPINNER! off last few / white black / >x pt (multi select?)
|
||||
|
||||
[] show PV on hint hover?
|
||||
|
||||
|
||||
lo prio
|
||||
[] more teaching / groups in danger? hard
|
||||
[] limit below board buttons somehow to not be too big
|
||||
[] box to label ? split in status and comment?
|
||||
[] List edit settings/object edit settings?
|
||||
|
||||
wont do
|
||||
[] List edit settings/object edit settings?
|
||||
[] Self-play tournaments?
|
||||
[] Multi engine (detect all req. analysis in analysis[engine] fn)
|
||||
[] 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
|
||||
|
||||
[] graph wonky on branch switch -> check in children &c.
|
||||
|
||||
bug/suggestion reports
|
||||
- The territory span B+something W+something was confusing: it looked like the last move was both of them and it took me a while to figure out what it was about. It would be maybe clearer if these numbers appeared as dynamic graduations on the left outside of the winrate window, like graduations on a ruler.
|
||||
--- Like, say if Black was 13pts ahead at some point in the game, the ruler would be: 15 / 10 / 5 / 0 / ... -15
|
||||
-- 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).
|
||||
_ 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?
|
||||
-- The UI would be directly understood if there were bubbles with a short explanation popping up when hovering above buttons and labels, e.g "Performing additional analysis to 1502 visits" which appears currently in the comments window after pushing the Extra button would also make a perfect explanatory bubble when hovering over said button too.
|
||||
-- Plus, explanatory bubbles would help to clarify labels like "Debug" (are there bugs in the matrix? :p), "enable_ownership" (which didn't seem to prevent or allow visualizing territory ownership on the board, as that was already covered by the "owner" checkbox anyway... so I guess I missed something), etc.
|
||||
-- That would also prevent the need to go through the official documentation for every little thing, making Katrain more user friendly, IMO.
|
||||
- In the 0.9 version I could hide dots from one player, which was handy to focus on my own mistakes when reviewing games, not my opponent's. For what I saw, we can't do that in the 1.0 version anymore, which is a shame IMO.
|
||||
-- Since dots help to discriminate between mistakes, it would be nice to be able to show only those that are big enough to worry about: for example showing only non-green dots, or showing only orange and red dots, or even only red dots, since strong and weak players will have different needs. In my case, when I was reviewing games (my own or classical Japanese ones) with the 0.9 version of Katrain, I would typically focus on moves that would be rated less than 80% efficient (ranging from orange to red, if I remember correctly). A slider going from 0 to 100% to show moves according to their efficiency would be handy, I think. Just an idea ^_^
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import heapq
|
||||
import math
|
||||
import random
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
|
||||
from common import OUTPUT_INFO, var_to_grid
|
||||
from game import Move
|
||||
|
||||
|
||||
def weighted_selection_without_replacement(items, m):
|
||||
"""For a list of arrays where the first element is a weight, returns random items with those weights, without replacement."""
|
||||
elt = [(math.log(random.random()) / item[0], item) for item in items]
|
||||
return [e[1] for e in heapq.nlargest(m, elt)] # NB fine if too small
|
||||
|
||||
|
||||
def dirichlet_noise(num, dir_alpha=0.3):
|
||||
return np.random.dirichlet([dir_alpha] * num)
|
||||
|
||||
|
||||
def ai_move(game, ai_settings):
|
||||
cn = game.current_node
|
||||
while not cn.analysis_ready:
|
||||
game.katrain.controls.set_status("Thinking...") # TODO: non blocking somehow?
|
||||
time.sleep(0.01)
|
||||
# select move
|
||||
candidate_ai_moves = cn.candidate_moves
|
||||
ai_mode = game.katrain.controls.ai_mode(cn.next_player)
|
||||
|
||||
if ("policy" in ai_mode or "p+" in ai_mode) and cn.policy:
|
||||
policy_moves = cn.policy_ranking
|
||||
size = game.board_size
|
||||
policy_grid = var_to_grid(cn.policy, size)
|
||||
legal_policy_moves = [(mv, pol) for mv, pol in policy_moves if not mv.is_pass if pol > 0]
|
||||
aimove = policy_moves[0][0]
|
||||
|
||||
if not aimove.is_pass:
|
||||
if "noise" in ai_mode:
|
||||
noise_str = ai_settings["noise_strength"]
|
||||
d_noise = dirichlet_noise(len(legal_policy_moves))
|
||||
noisy_policy_moves = [(mv, (1 - noise_str) * pol + noise_str * noise) for ((mv, pol), noise) in zip(legal_policy_moves, d_noise)]
|
||||
aimove = max(noisy_policy_moves, key=lambda mp: mp[1])[0]
|
||||
if "local" in ai_mode or "tenuki" in ai_mode or "pick" in ai_mode and cn.single_move and cn.single_move.coords:
|
||||
var = ai_settings["local_stddev"] ** 2
|
||||
n_moves = int(ai_settings["pick_frac"] * len(legal_policy_moves) + ai_settings["pick_n"])
|
||||
mx, my = cn.single_move.coords
|
||||
top_5_pass = any([polmove[0].is_pass for polmove in policy_moves[:5]]) # dont make it jump around for the last few sensible non pass moves
|
||||
if not top_5_pass:
|
||||
if "local" in ai_mode:
|
||||
weighted_coords = [
|
||||
(math.exp(-0.5 * ((x - mx) ** 2 + (y - my) ** 2) / var), x, y, policy_grid[y][x])
|
||||
for x in range(size[0])
|
||||
for y in range(size[1])
|
||||
if policy_grid[y][x] > 0
|
||||
]
|
||||
else: # if "pick" in ai_mode -> even
|
||||
weighted_coords = [(1, x, y, policy_grid[y][x]) for x in range(size[0]) for y in range(size[1]) if policy_grid[y][x] > 0]
|
||||
pick_moves = weighted_selection_without_replacement(weighted_coords, n_moves)
|
||||
if pick_moves:
|
||||
best = max(pick_moves, key=lambda m: m[3])
|
||||
aimove = Move((best[1], best[2]), player=cn.next_player)
|
||||
game.katrain.log(
|
||||
f"{aimove} was top from pick moves starting with {[Move((best[1], best[2]), player=cn.next_player).gtp() for best in pick_moves[:10]]} out of {len(pick_moves)} "
|
||||
)
|
||||
else:
|
||||
aimove = Move(None, player=cn.next_player) # pass
|
||||
else:
|
||||
weighted_coords = [(policy_grid[y][x], x, y, policy_grid[y][x]) for x in range(size[0]) for y in range(size[1]) if policy_grid[y][x] > 0]
|
||||
aimove = Move(weighted_selection_without_replacement(weighted_coords, 1)[0][1:3], player=cn.next_player) # just take a random move by policy w/o noise
|
||||
elif "balance" in ai_mode and candidate_ai_moves[0]["move"] != "pass": # don't play suicidal to balance score - pass when it's best
|
||||
sign = cn.player_sign(cn.next_player) # TODO check
|
||||
sel_moves = [ # top move, or anything not too bad, or anything that makes you still ahead
|
||||
move
|
||||
for i, move in enumerate(candidate_ai_moves)
|
||||
if i == 0
|
||||
or move["visits"] >= ai_settings["balance_min_visits"]
|
||||
and (
|
||||
move["pointsLost"] < ai_settings["balance_random_loss"]
|
||||
or move["pointsLost"] < ai_settings["balance_max_loss"]
|
||||
and sign * move["scoreLead"] > ai_settings["balance_target_score"]
|
||||
)
|
||||
]
|
||||
aimove = Move.from_gtp(random.choice(sel_moves)["move"], player=cn.next_player) # TODO: could be weighted towards worse
|
||||
elif "jigo" in ai_mode and candidate_ai_moves[0]["move"] != "pass":
|
||||
sign = cn.player_sign(cn.next_player) # TODO check
|
||||
jigo_move = min(candidate_ai_moves, key=lambda move: abs(sign * move["scoreLead"] - 0.5))
|
||||
aimove = Move.from_gtp(jigo_move["move"], player=cn.next_player)
|
||||
else:
|
||||
if "default" not in ai_mode:
|
||||
game.katrain.log(f"Unknown AI mode {ai_mode} or policy missing, using default.", OUTPUT_INFO)
|
||||
aimove = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player)
|
||||
print("COORDS", aimove.coords)
|
||||
game.play(aimove)
|
||||
@@ -0,0 +1,14 @@
|
||||
OUTPUT_ERROR = -1
|
||||
OUTPUT_INFO = 0
|
||||
OUTPUT_DEBUG = 1
|
||||
OUTPUT_EXTRA_DEBUG = 2
|
||||
|
||||
|
||||
def var_to_grid(array_var, size):
|
||||
"""convert ownership/policy to grid format such that grid[y][x] is for move with coords x,y"""
|
||||
ix = 0
|
||||
grid = [[]] * size[1]
|
||||
for y in range(size[1] - 1, -1, -1):
|
||||
grid[y] = array_var[ix : ix + size[0]]
|
||||
ix += size[0]
|
||||
return grid
|
||||
+10
-5
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"engine": {
|
||||
"katago": "KataGo/katago-bs",
|
||||
"katago": "../KataGo/cpp/katago",
|
||||
"model": " models/b15-1.3.2.txt.gz",
|
||||
"config": "KataGo/analysis_config.cfg",
|
||||
"threads": 8,
|
||||
"max_visits": 500,
|
||||
"max_visits": 5,
|
||||
"max_time": 3.0,
|
||||
"enable_ownership": true
|
||||
},
|
||||
@@ -34,12 +34,17 @@
|
||||
1.5,
|
||||
0.5
|
||||
],
|
||||
"eval_off_show_last": 3
|
||||
},
|
||||
"ai": {
|
||||
"balance_target_score": 2,
|
||||
"balance_random_loss": 1,
|
||||
"balance_max_loss": 5,
|
||||
"balance_min_visits": 20,
|
||||
"noise_strength": 0.07,
|
||||
"eval_off_show_last": 3
|
||||
"noise_strength": 0.8,
|
||||
"pick_n": 10,
|
||||
"pick_frac": 0.2,
|
||||
"local_stddev": 10
|
||||
},
|
||||
"board_ui": {
|
||||
"starpoint_size": 0.1,
|
||||
@@ -112,7 +117,7 @@
|
||||
0
|
||||
],
|
||||
"policy_color": [
|
||||
0,
|
||||
0.9,
|
||||
0.2,
|
||||
0.8
|
||||
]
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
OUTPUT_ERROR = -1
|
||||
OUTPUT_INFO = 0
|
||||
OUTPUT_DEBUG = 1
|
||||
OUTPUT_EXTRA_DEBUG = 2
|
||||
@@ -6,7 +6,7 @@ import threading
|
||||
import time
|
||||
from typing import Callable, Optional
|
||||
|
||||
from constants import OUTPUT_DEBUG, OUTPUT_ERROR
|
||||
from common import OUTPUT_DEBUG, OUTPUT_ERROR
|
||||
from game_node import GameNode
|
||||
|
||||
|
||||
@@ -66,10 +66,17 @@ class KataGoEngine:
|
||||
if not line:
|
||||
continue
|
||||
analysis = json.loads(line)
|
||||
if analysis["id"] in self.queries:
|
||||
callback, start_time, next_move = self.queries[analysis["id"]]
|
||||
else:
|
||||
self.katrain.log(f"Query result {analysis['id']} discarded -- recent new game?", OUTPUT_DEBUG)
|
||||
continue
|
||||
if "error" in analysis:
|
||||
self.katrain.log(f"{analysis} received from KataGo", OUTPUT_ERROR)
|
||||
elif analysis["id"] in self.queries:
|
||||
callback, start_time = self.queries[analysis["id"]]
|
||||
if not (next_move is None and "Illegal move" in analysis["error"]): # sweep
|
||||
self.katrain.log(f"{analysis} received from KataGo", OUTPUT_ERROR)
|
||||
continue
|
||||
else:
|
||||
callback, start_time, next_move = self.queries[analysis["id"]]
|
||||
time_taken = time.time() - start_time
|
||||
self.katrain.log(
|
||||
f"[{time_taken:.1f}][{analysis['id']}] KataGo Analysis Received: {analysis.keys()} {line[:80]}...", OUTPUT_DEBUG,
|
||||
@@ -77,14 +84,12 @@ class KataGoEngine:
|
||||
callback(analysis)
|
||||
del self.queries[analysis["id"]]
|
||||
self.katrain.update_state()
|
||||
else:
|
||||
self.katrain.log(f"Query result {analysis['id']} discarded -- recent new game?", OUTPUT_DEBUG)
|
||||
|
||||
def send_query(self, query, callback):
|
||||
def send_query(self, query, callback, next_move):
|
||||
self.query_counter += 1
|
||||
if "id" not in query:
|
||||
query["id"] = f"QUERY:{str(self.query_counter)}"
|
||||
self.queries[query["id"]] = (callback, time.time())
|
||||
self.queries[query["id"]] = (callback, time.time(), next_move)
|
||||
if self.katago_process:
|
||||
self.katrain.log(f"Sending query {query['id']}: {str(query)}", OUTPUT_DEBUG)
|
||||
self.katago_process.stdin.write((json.dumps(query) + "\n").encode())
|
||||
@@ -113,4 +118,4 @@ class KataGoEngine:
|
||||
"overrideSettings": {"maxTime": self.config["max_time"] if time_limit else 1000.0}
|
||||
# "overrideSettings": {"playoutDoublingAdvantage": 3.0, "playoutDoublingAdvantagePla": 'BLACK' if not moves or moves[-1].player == 'W' else "WHITE"}
|
||||
}
|
||||
self.send_query(query, callback)
|
||||
self.send_query(query, callback, next_move)
|
||||
@@ -1,13 +1,14 @@
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from kivy.clock import Clock
|
||||
|
||||
from constants import OUTPUT_DEBUG, OUTPUT_INFO
|
||||
from common import OUTPUT_DEBUG, OUTPUT_INFO
|
||||
from game_node import GameNode
|
||||
from sgf_parser import SGF, Move
|
||||
|
||||
@@ -200,51 +201,41 @@ class Game:
|
||||
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}"
|
||||
|
||||
def write_sgf(self, path=None):
|
||||
file_name = os.path.join(path, f"katrain_{self.game_id}.sgf")
|
||||
black = re.sub(r"['<>:\"/\\|?*]", "", self.root.get_first("PB"))
|
||||
white = re.sub(r"['<>:\"/\\|?*]", "", self.root.get_first("PW"))
|
||||
white = self.root.get_first("PW")
|
||||
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)
|
||||
with open(file_name, "w") as f:
|
||||
f.write(self.root.sgf())
|
||||
return f"SGF with analysis written to {file_name}"
|
||||
|
||||
def ai_move(self, train_settings):
|
||||
def analyze_extra(self, mode):
|
||||
stones = {s.coords for s in self.stones}
|
||||
cn = self.current_node
|
||||
while not cn.analysis_ready:
|
||||
self.katrain.controls.set_status("Thinking...") # TODO: non blocking somehow?
|
||||
time.sleep(0.01)
|
||||
# select move
|
||||
candidate_ai_moves = cn.candidate_moves
|
||||
ai_mode = self.katrain.controls.ai_mode(cn.next_player)
|
||||
if not cn.analysis:
|
||||
self.katrain.controls.set_status("Wait for initial analysis to complete before doing a board-sweep or refinement", self.current_node)
|
||||
return
|
||||
|
||||
if ("policy" in ai_mode or "noise" in ai_mode) and cn.policy:
|
||||
policy_moves = cn.policy_ranking
|
||||
aimove = policy_moves[0][0]
|
||||
if not aimove.is_pass and "noise" in ai_mode:
|
||||
noise = train_settings["noise_strength"]
|
||||
policy_moves = [(mv, pol + random.gauss(0, noise)) for mv, pol in policy_moves if not mv.is_pass]
|
||||
aimove = max(policy_moves, key=lambda mp: mp[1])[0]
|
||||
elif "balance" in ai_mode and candidate_ai_moves[0]["move"] != "pass": # don't play suicidal to balance score - pass when it's best
|
||||
sign = cn.player_sign(cn.next_player) # TODO check
|
||||
sel_moves = [ # top move, or anything not too bad, or anything that makes you still ahead
|
||||
move
|
||||
for i, move in enumerate(candidate_ai_moves)
|
||||
if i == 0
|
||||
or move["visits"] >= train_settings["balance_min_visits"]
|
||||
and (
|
||||
move["pointsLost"] < train_settings["balance_random_loss"]
|
||||
or move["pointsLost"] < train_settings["balance_max_loss"]
|
||||
and sign * move["scoreLead"] > train_settings["balance_target_score"]
|
||||
)
|
||||
]
|
||||
aimove = Move.from_gtp(random.choice(sel_moves)["move"], player=cn.next_player) # TODO: could be weighted towards worse
|
||||
elif "jigo" in ai_mode and candidate_ai_moves[0]["move"] != "pass":
|
||||
sign = cn.player_sign(cn.next_player) # TODO check
|
||||
jigo_move = min(candidate_ai_moves, key=lambda move: abs(sign * move["scoreLead"] - 0.5))
|
||||
aimove = Move.from_gtp(jigo_move["move"], player=cn.next_player)
|
||||
else:
|
||||
if "default" not in ai_mode:
|
||||
self.katrain.log(f"Unknown AI mode {ai_mode} or policy missing, using default.", OUTPUT_INFO)
|
||||
aimove = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player)
|
||||
self.play(aimove)
|
||||
if mode == "extra":
|
||||
visits = cn.analysis["root"]["visits"] + self.engine.config["max_visits"]
|
||||
self.katrain.controls.set_status(f"Performing additional analysis to {visits} visits")
|
||||
cn.analyze(self.engine, visits=visits, priority=-1_000, time_limit=False)
|
||||
return
|
||||
elif mode == "sweep":
|
||||
board_size_x, board_size_y = self.board_size
|
||||
analyze_moves = [Move(coords=(x, y), player=cn.next_player) for x in range(board_size_x) for y in range(board_size_y) if (x, y) not in stones]
|
||||
visits = int(self.engine.config["max_visits"] * self.config["sweep_visits_frac"] + 0.5)
|
||||
self.katrain.controls.set_status(f"Refining analysis of entire board to {visits} visits")
|
||||
priority = -1_000_000_000
|
||||
else: # mode=='equalize':
|
||||
analyze_moves = [Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items()]
|
||||
visits = max(d["visits"] for d in cn.analysis["moves"].values())
|
||||
self.katrain.controls.set_status(f"Equalizing analysis of candidate moves to {visits} visits")
|
||||
priority = -1_000
|
||||
for move in analyze_moves:
|
||||
cn.analyze(self.engine, priority, visits=visits, refine_move=move, time_limit=False) # explicitly requested so take as long as you need
|
||||
|
||||
def analyze_undo(self, node, train_config):
|
||||
move = node.single_move
|
||||
@@ -272,29 +263,3 @@ class Game:
|
||||
self.undo(1)
|
||||
self.katrain.controls.set_status(f"Undid move {move.gtp()} as it lost {points_lost:.1f} points{xmsg}")
|
||||
self.katrain.update_state()
|
||||
|
||||
def analyze_extra(self, mode):
|
||||
stones = {s.coords for s in self.stones}
|
||||
cn = self.current_node
|
||||
if not cn.analysis:
|
||||
self.katrain.controls.set_status("Wait for initial analysis to complete before doing a board-sweep or refinement", self.current_node)
|
||||
return
|
||||
|
||||
if mode == "extra":
|
||||
visits = cn.analysis["root"]["visits"] + self.engine.config["max_visits"]
|
||||
self.katrain.controls.set_status(f"Performing additional analysis to {visits} visits")
|
||||
cn.analyze(self.engine, visits=visits, priority=-1_000, time_limit=False)
|
||||
return
|
||||
elif mode == "sweep":
|
||||
board_size_x, board_size_y = self.board_size
|
||||
analyze_moves = [Move(coords=(x, y), player=cn.next_player) for x in range(board_size_x) for y in range(board_size_y) if (x, y) not in stones]
|
||||
visits = int(self.engine.config["max_visits"] * self.config["sweep_visits_frac"] + 0.5)
|
||||
self.katrain.controls.set_status(f"Refining analysis of entire board to {visits} visits")
|
||||
priority = -1_000_000_000
|
||||
else: # mode=='equalize':
|
||||
analyze_moves = [Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items()]
|
||||
visits = max(d["visits"] for d in cn.analysis["moves"].values())
|
||||
self.katrain.controls.set_status(f"Equalizing analysis of candidate moves to {visits} visits")
|
||||
priority = -1_000
|
||||
for move in analyze_moves:
|
||||
cn.analyze(self.engine, priority, visits=visits, refine_move=move, time_limit=False) # explicitly requested so take as long as you need
|
||||
+14
-7
@@ -6,9 +6,10 @@ from kivy.properties import ListProperty
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
from constants import OUTPUT_DEBUG
|
||||
from common import OUTPUT_DEBUG
|
||||
from game import Move
|
||||
from gui.kivyutils import draw_circle, draw_text
|
||||
from common import var_to_grid
|
||||
|
||||
|
||||
class BadukPanWidget(Widget):
|
||||
@@ -28,6 +29,9 @@ class BadukPanWidget(Widget):
|
||||
return sorted([(abs(p - pos), i) for i, p in enumerate(gridpos)])[0]
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if touch.button != "left":
|
||||
return
|
||||
|
||||
if not self.gridpos_x:
|
||||
return
|
||||
xd, xp = self._find_closest(touch.x, self.gridpos_x)
|
||||
@@ -44,6 +48,9 @@ class BadukPanWidget(Widget):
|
||||
return self.on_touch_down(touch)
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
if touch.button != "left":
|
||||
return
|
||||
|
||||
if not self.gridpos_x:
|
||||
return
|
||||
katrain = self.katrain
|
||||
@@ -199,22 +206,22 @@ class BadukPanWidget(Widget):
|
||||
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"):
|
||||
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
|
||||
pass_btn.canvas.after.clear()
|
||||
if katrain.controls.policy.active and policy:
|
||||
ix = 0
|
||||
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):
|
||||
if policy[ix] > 0:
|
||||
polsize = math.sqrt(policy[ix])
|
||||
if policy_grid[y][x] > 0:
|
||||
polsize = math.sqrt(policy_grid[y][x])
|
||||
policy_circle_color = (
|
||||
*self.ui_config["policy_color"],
|
||||
self.ui_config["ghost_alpha"] + self.ui_config["top_move_x_alpha"] * (policy[ix] == best_move_policy),
|
||||
self.ui_config["ghost_alpha"] + self.ui_config["top_move_x_alpha"] * (policy_grid[y][x] == best_move_policy),
|
||||
)
|
||||
self.draw_stone(x, y, policy_circle_color, scale=polsize)
|
||||
ix = ix + 1
|
||||
polsize = math.sqrt(policy[ix])
|
||||
polsize = math.sqrt(policy[-1])
|
||||
with pass_btn.canvas.after:
|
||||
draw_circle((pass_btn.pos[0] + pass_btn.width / 2, pass_btn.pos[1] + pass_btn.height / 2), polsize * pass_btn.height / 2, self.ui_config["policy_color"])
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ class LabelledCheckBox(CheckBox):
|
||||
def __init__(self, text=None, **kwargs):
|
||||
if text is not None:
|
||||
kwargs["active"] = bool(text)
|
||||
print("CB", text, kwargs)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@property
|
||||
@@ -209,6 +210,7 @@ class ScoreGraph(Label):
|
||||
if math.isnan(dot_point[1]):
|
||||
dot_point[1] = self.pos[1] + available_height / 2 * (1 + (nn_values or [0])[-1] / scale)
|
||||
self.dot_pos = [c - self.highlight_size / 2 for c in dot_point]
|
||||
print("Graph updated to ", len(line_points), "points, hl=", self.highlighted_index, self.dot_pos)
|
||||
|
||||
def update_value(self, node):
|
||||
self.highlighted_index = index = node.depth
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.label import Label
|
||||
|
||||
from constants import OUTPUT_DEBUG, OUTPUT_ERROR
|
||||
from common import OUTPUT_DEBUG, OUTPUT_ERROR
|
||||
from engine import KataGoEngine
|
||||
from game import Game, GameNode
|
||||
from gui.kivyutils import (
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
#:import ew kivy.uix.effectwidget
|
||||
|
||||
|
||||
#:set AI_MODES ['Default','Balance','Jigo','Policy','P+Noise']
|
||||
#:set AI_MODES ['Default','Balance','Jigo','Policy','P+Pick','P+Local','P+Noise']
|
||||
#:set PLAYER_MODES ['Human', 'Teach','AI:']
|
||||
#:set PLAYER_MODE_VALUES ['human','human+undo','ai']
|
||||
#:set BUTTON_COLOR [0.23, 0.30, 0.35, 1]
|
||||
|
||||
+14
-8
@@ -1,20 +1,23 @@
|
||||
from kivy.config import Config # isort:skip
|
||||
|
||||
Config.set("input", "mouse", "mouse,multitouch_on_demand") # isort:skip # no red dots on right click
|
||||
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
from queue import Queue
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.clipboard import Clipboard
|
||||
from kivy.core.window import Window
|
||||
from kivy.lang import Builder
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.popup import Popup
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
from constants import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_INFO
|
||||
from ai import ai_move
|
||||
from common import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_INFO
|
||||
from engine import KataGoEngine
|
||||
from game import Game, IllegalMoveException, KaTrainSGF, Move
|
||||
from gui import *
|
||||
@@ -58,8 +61,8 @@ class KaTrainGui(BoxLayout):
|
||||
sys.exit(1)
|
||||
|
||||
def save_config(self):
|
||||
for k,v in self._config.items():
|
||||
self._config_store.put(k,**v)
|
||||
for k, v in self._config.items():
|
||||
self._config_store.put(k, **v)
|
||||
|
||||
def config(self, setting, default=None):
|
||||
try:
|
||||
@@ -117,6 +120,7 @@ class KaTrainGui(BoxLayout):
|
||||
self.update_state()
|
||||
except Exception as e:
|
||||
self.log(f"Exception in processing message {msg} {args}: {e}", OUTPUT_ERROR)
|
||||
traceback.print_exc()
|
||||
|
||||
def __call__(self, message, *args):
|
||||
if self.game:
|
||||
@@ -130,7 +134,7 @@ class KaTrainGui(BoxLayout):
|
||||
|
||||
def _do_ai_move(self, node=None):
|
||||
if node is None or self.game.current_node == node:
|
||||
self.game.ai_move(self.config("trainer"))
|
||||
ai_move(self.game, self.config("ai"))
|
||||
|
||||
def _do_undo(self, n_times=1):
|
||||
self.game.undo(n_times)
|
||||
@@ -184,7 +188,9 @@ class KaTrainGui(BoxLayout):
|
||||
for pl in Move.PLAYERS:
|
||||
if not self.game.root.get_first(f"P{pl}"):
|
||||
_, model_file = os.path.split(self.engine.config["model"])
|
||||
self.game.root.properties[f"P{pl}"] = [f"KaTrain (KataGo {model_file})" if "ai" in self.controls.player_mode(pl) else "Player"] # TODO: more dynamic?
|
||||
self.game.root.properties[f"P{pl}"] = [
|
||||
f"AI {self.controls.ai_mode(pl)} (KataGo { os.path.splitext(model_file)[0]})" if "ai" in self.controls.player_mode(pl) else "Player"
|
||||
] # TODO: more dynamic?
|
||||
msg = self.game.write_sgf(self.config("files/sgf_save"))
|
||||
self.log(msg, OUTPUT_INFO)
|
||||
self.controls.set_status(msg)
|
||||
|
||||
Reference in new issue
Block a user