readme update and undo lock option

This commit is contained in:
Sander Land committed 2020-02-15 19:05:55 +01:00
1 parent 136dff4f1c
commit d57e6ce87d
5 files changed
+37 -11

No files matched your search

+18 -2
View File
@@ -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.
![screenshot](https://i.imgur.com/2T2b6qL.png)
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
------------- -------------
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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