readme update and undo lock option
This commit is contained in:
1 parent
136dff4f1c
commit
d57e6ce87d
5 files changed
+37
-11
No files matched your search
@@ -1,3 +1,19 @@
|
|||||||
|
Introduction
|
||||||
|
============
|
||||||
|
This repository contains tool for playing go with AI feedback aimed at kyu players.
|
||||||
|
The idea is to give immediate feedback on the many large mistakes we make in terms of inefficient moves.
|
||||||
|
It is based on the KataGo AI and relies heavily on score estimation rather than win rate.
|
||||||
|
|
||||||
|
Some uses include:
|
||||||
|
|
||||||
|
* Analyze your games to find the moves that were most costly in terms of points lost.
|
||||||
|
* Play against AI and get immediate feedback on mistakes with option to retry.
|
||||||
|
* Play against a stronger player and use the retry option instead of handicap stones.
|
||||||
|
* Play a match with an evenly matched friend where both players get instant feedback.
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Manual
|
Manual
|
||||||
======
|
======
|
||||||
|
|
||||||
@@ -23,7 +39,7 @@ Options
|
|||||||
* Undo: automatically undo poor moves for this player and make them try again.
|
* Undo: automatically undo poor moves for this player and make them try again.
|
||||||
* AI: let the AI control this player. Check both for self-play.
|
* AI: let the AI control this player. Check both for self-play.
|
||||||
* Show owner: show expected control of territory.
|
* Show owner: show expected control of territory.
|
||||||
* Lock AI: disallow extra undos, changing hints options, changing auto move, or AI move.
|
* Lock AI: disallow extra undos, changing hints options, changing auto move, or AI move. Also turns off the option to click on a move to see detailed comments.
|
||||||
* Fast: use a lower number of max visits for evaluation/AI move.
|
* Fast: use a lower number of max visits for evaluation/AI move.
|
||||||
* Balance score: Deliberately make sub-optimal moves as the AI in an attempt to balance the score towawrds a slight win.
|
* Balance score: Deliberately make sub-optimal moves as the AI in an attempt to balance the score towawrds a slight win.
|
||||||
|
|
||||||
@@ -58,7 +74,7 @@ Play
|
|||||||
* Click `Analyze`
|
* Click `Analyze`
|
||||||
|
|
||||||
* Save game
|
* Save game
|
||||||
* Click save to get an sgf as `out.sgf` with comments (and a short version in the text box).
|
* Click save to get an sgf with comments saved in the sgfout/ directory (and a short version in the text box).
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
|
|||||||
@@ -315,8 +315,8 @@ class Board:
|
|||||||
return "\n".join("".join(Move.PLAYERS[self.chains[c][0].player] if c >= 0 else "-" for c in l) for l in self.board) + f"\ncaptures: {self.prisoner_count}"
|
return "\n".join("".join(Move.PLAYERS[self.chains[c][0].player] if c >= 0 else "-" for c in l) for l in self.board) + f"\ncaptures: {self.prisoner_count}"
|
||||||
|
|
||||||
def write_sgf(self, komi, train_settings, file_name=None):
|
def write_sgf(self, komi, train_settings, file_name=None):
|
||||||
def sgfify(mvs):
|
def sgfify(mvs, comment=""):
|
||||||
return f"(;GM[1]FF[4]SZ[{self.board_size}]KM[{komi}]RU[JP];" + ";".join(mvs) + ")"
|
return f"(;GM[1]FF[4]SZ[{self.board_size}]KM[{komi}]RU[JP];" + ";".join(mvs) + (f"C[{comment}])" if comment else "")
|
||||||
|
|
||||||
def format_move(move, prev_move):
|
def format_move(move, prev_move):
|
||||||
undos = [m for m in prev_move.children if m != move]
|
undos = [m for m in prev_move.children if m != move]
|
||||||
@@ -349,4 +349,4 @@ class Board:
|
|||||||
pass
|
pass
|
||||||
with open(file_name, "w") as f:
|
with open(file_name, "w") as f:
|
||||||
f.write(sgfify(sgfmoves))
|
f.write(sgfify(sgfmoves))
|
||||||
return sgfify(sgfmoves_small)
|
return sgfify(sgfmoves_small, f"SGF with analysis written to {file_name}")
|
||||||
+2
-1
@@ -39,7 +39,8 @@
|
|||||||
"undo_eval_threshold": 0.875,
|
"undo_eval_threshold": 0.875,
|
||||||
"undo_point_threshold": 1,
|
"undo_point_threshold": 1,
|
||||||
"num_undo_prompts": 1,
|
"num_undo_prompts": 1,
|
||||||
"sgf_show_best_move_threshold": 0.95
|
"sgf_show_best_move_threshold": 0.95,
|
||||||
|
"dont_lock_undos": false
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"level": 1
|
"level": 1
|
||||||
|
|||||||
+10
-3
@@ -36,6 +36,7 @@ class EngineControls(GridLayout):
|
|||||||
self.komi = 6.5 # loaded from config in init
|
self.komi = 6.5 # loaded from config in init
|
||||||
self.outstanding_analysis_queries = [] # allows faster interaction while kata is starting
|
self.outstanding_analysis_queries = [] # allows faster interaction while kata is starting
|
||||||
self.kata = None
|
self.kata = None
|
||||||
|
self.query_time = {}
|
||||||
|
|
||||||
def redraw(self, include_board=False):
|
def redraw(self, include_board=False):
|
||||||
if include_board:
|
if include_board:
|
||||||
@@ -153,7 +154,12 @@ class EngineControls(GridLayout):
|
|||||||
self.play(aimove)
|
self.play(aimove)
|
||||||
|
|
||||||
def _do_undo(self):
|
def _do_undo(self):
|
||||||
if self.ai_lock.active and self.auto_undo.active(self.board.current_move.player) and len(self.board.current_move.parent.children) > self.train_settings["num_undo_prompts"]:
|
if (
|
||||||
|
self.ai_lock.active
|
||||||
|
and self.auto_undo.active(self.board.current_move.player)
|
||||||
|
and len(self.board.current_move.parent.children) > self.train_settings["num_undo_prompts"]
|
||||||
|
and not self.train_settings.get("dont_lock_undos")
|
||||||
|
):
|
||||||
self.info.text = f"Can't undo more than {self.train_settings['num_undo_prompts']} time(s) when locked"
|
self.info.text = f"Can't undo more than {self.train_settings['num_undo_prompts']} time(s) when locked"
|
||||||
return
|
return
|
||||||
self.board.undo()
|
self.board.undo()
|
||||||
@@ -183,8 +189,6 @@ class EngineControls(GridLayout):
|
|||||||
while self.outstanding_analysis_queries:
|
while self.outstanding_analysis_queries:
|
||||||
self._send_analysis_query(self.outstanding_analysis_queries.pop(0))
|
self._send_analysis_query(self.outstanding_analysis_queries.pop(0))
|
||||||
line = self.kata.stdout.readline()
|
line = self.kata.stdout.readline()
|
||||||
if self.debug:
|
|
||||||
print("KATA ANALYSIS RECEIVED:", line[:80], "...")
|
|
||||||
if not line: # occasionally happens?
|
if not line: # occasionally happens?
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@@ -192,10 +196,13 @@ class EngineControls(GridLayout):
|
|||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
print(f"JSON decode error: '{e}' encountered after receiving input '{line}'")
|
print(f"JSON decode error: '{e}' encountered after receiving input '{line}'")
|
||||||
return
|
return
|
||||||
|
if self.debug:
|
||||||
|
print(f"[{time.time()-self.query_time.get(analysis['id'],0):.1f}] kata analysis received:", line[:80], "...")
|
||||||
self.board.store_analysis(analysis)
|
self.board.store_analysis(analysis)
|
||||||
self.update_evaluation()
|
self.update_evaluation()
|
||||||
|
|
||||||
def _send_analysis_query(self, query):
|
def _send_analysis_query(self, query):
|
||||||
|
self.query_time[query["id"]] = time.time()
|
||||||
if self.kata:
|
if self.kata:
|
||||||
self.kata.stdin.write((json.dumps(query) + "\n").encode())
|
self.kata.stdin.write((json.dumps(query) + "\n").encode())
|
||||||
self.kata.stdin.flush()
|
self.kata.stdin.flush()
|
||||||
|
|||||||
+4
-2
@@ -53,11 +53,13 @@ class BadukPanWidget(Widget):
|
|||||||
yd, yp = self._find_closest(touch.y)
|
yd, yp = self._find_closest(touch.y)
|
||||||
stones_here = [m for m in self.engine.board.stones if m.coords == (xp, yp)]
|
stones_here = [m for m in self.engine.board.stones if m.coords == (xp, yp)]
|
||||||
if stones_here and max(yd, xd) < self.grid_size / 2: # load old comment
|
if stones_here and max(yd, xd) < self.grid_size / 2: # load old comment
|
||||||
self.engine.info.text = stones_here[-1].comment(sgf=True)
|
|
||||||
self.engine.show_evaluation_stats(stones_here[-1])
|
|
||||||
if self.engine.debug:
|
if self.engine.debug:
|
||||||
print("\nAnalysis:\n", stones_here[-1].analysis)
|
print("\nAnalysis:\n", stones_here[-1].analysis)
|
||||||
print("\nParent Analysis:\n", stones_here[-1].parent.analysis)
|
print("\nParent Analysis:\n", stones_here[-1].parent.analysis)
|
||||||
|
if not self.engine.ai_lock.active:
|
||||||
|
self.engine.info.text = stones_here[-1].comment(sgf=True)
|
||||||
|
self.engine.show_evaluation_stats(stones_here[-1])
|
||||||
|
|
||||||
self.ghost_stone = None
|
self.ghost_stone = None
|
||||||
self.redraw() # remove ghost
|
self.redraw() # remove ghost
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user