commit d9952afc99f22175419f413c2370ce4fc2135421
parent 749eb9f69ad3e4f81fb0f960e7eda980b5e3a506
Author: Thomas Vigouroux <me@vigoux.eu>
Date: Fri, 26 Apr 2024 15:39:39 +0200
feat: start katago integration
Diffstat:
A | katago.go | | | 145 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | main.go | | | 88 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- |
2 files changed, 222 insertions(+), 11 deletions(-)
diff --git a/katago.go b/katago.go
@@ -0,0 +1,145 @@
+package main
+
+import (
+ "log"
+ "os/exec"
+ "encoding/json"
+ "bufio"
+ "fmt"
+ "io"
+ "sync"
+)
+
+type Katago struct {
+ Process *exec.Cmd
+ InChan io.WriteCloser
+ ChanMap map[string]chan<- Response
+ RCount uint
+ WMutex sync.Mutex
+}
+
+type Request struct {
+ Id string `json:"id"`
+ Moves [][]string `json:"moves"`
+ InitialStones [][]string `json:"initialStones"`
+ Rules string `json:"rules"`
+ Komi float64 `json:"komi"`
+ XSize int `json:"boardXSize"`
+ YSize int `json:"boardYSize"`
+ Report float64 `json:"reportDuringSearchEvery,omitempty"`
+ // TODO: add the many other options
+}
+
+type Response struct {
+ Id string `json:"id"`
+ IsDuringSearch bool `json:"isDuringSearch"`
+ TurnNumber int `json:"turnNumber"`
+ moveInfos []any
+ rootInfo any
+ ownership any
+ ownershipStdev any
+ policy any
+}
+
+func (k *Katago) request(r Request) (<-chan Response, error) {
+ r.Id = fmt.Sprint("req", k.RCount)
+ k.RCount++
+
+ channel := make(chan Response)
+ k.ChanMap[r.Id] = channel
+
+ bytes, err := json.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+
+ k.WMutex.Lock()
+ _, err = k.InChan.Write(bytes)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = k.InChan.Write([]byte("\n"))
+ if err != nil {
+ return nil, err
+ }
+ k.WMutex.Unlock()
+
+ return channel, nil
+}
+
+func newKatago() (*Katago, error) {
+ cmd := exec.Command("./katago/katago", "analysis", "-config", "katago/analysis_example.cfg")
+
+ stdinpipe, err := cmd.StdinPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ stdoutpipe, err := cmd.StdoutPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ stderrpipe, err := cmd.StderrPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if err = cmd.Start(); err != nil {
+ return nil, err
+ }
+ log.Println("KataGo started")
+
+ ret := new(Katago)
+ ret.Process = cmd
+ ret.ChanMap = make(map[string](chan<- Response))
+ ret.InChan = stdinpipe
+ ret.WMutex = sync.Mutex{}
+
+ // Begin listening on stdout
+ go func() {
+ jsonreader := json.NewDecoder(stdoutpipe)
+ for {
+ var resp Response
+ log.Println("----------------------- Listening to KataGo")
+
+ err := jsonreader.Decode(&resp)
+
+ if err != nil {
+ log.Fatal(err)
+ } else if err == io.EOF {
+ break
+ }
+
+ channel, ok := ret.ChanMap[resp.Id]
+
+ if !ok {
+ log.Println("No corresponding channel ", resp.Id)
+ continue
+ }
+
+ channel <- resp
+ log.Print("Sent resp ", resp)
+ // TODO: close channel after final response
+ // if !resp.IsDuringSearch {
+ // close(channel)
+ // ret.ChanMap[resp.Id] = nil
+ // }
+ }
+ log.Println("Finished")
+ }()
+
+ go func() {
+ errscanner := bufio.NewScanner(stderrpipe)
+ log.Println("Listening to KataGo stderr")
+ for errscanner.Scan() {
+ txt := errscanner.Text()
+ log.Println("KataGo Err ", txt)
+ }
+ }()
+ //
+ // ret.InChan.Write([]byte("{\"id\":\"foo\",\"action\":\"query_version\"}\n"))
+
+ return ret, nil
+}
diff --git a/main.go b/main.go
@@ -3,7 +3,9 @@ package main
import (
"image"
"image/color"
+ "strings"
// "image/png"
+ "fmt"
"log"
"os"
@@ -29,12 +31,17 @@ import (
type App struct {
Node *sgf.Node
+ Katago *Katago
Editor material.EditorStyle
}
func (g *App) SetNode(node *sgf.Node) {
if g.Node != nil {
- g.Node.SetValue("C", g.Editor.Editor.Text())
+ newcomment := g.Editor.Editor.Text()
+ _, present := g.Node.GetValue("C")
+ if newcomment != "" || present {
+ g.Node.SetValue("C", newcomment)
+ }
}
g.Node = node
@@ -44,6 +51,58 @@ func (g *App) SetNode(node *sgf.Node) {
} else {
g.Editor.Editor.SetText("")
}
+
+ go g.AnalyzeCurrent()
+}
+
+func (g *App) AnalyzeCurrent() {
+ root := g.Node.GetRoot()
+ board := root.Board()
+ rules, _ := root.GetValue("RU")
+
+ line := g.Node.GetLine()
+
+ moves := make([][]string, 0)
+ for _, inner := range line {
+ move, iswhite := inner.GetValue("W")
+ var color string
+ var isblack bool
+ if iswhite {
+ color = "W"
+ } else if move, isblack = inner.GetValue("B"); isblack {
+ color = "B"
+ }
+
+ if iswhite || isblack {
+ x, y, _ := sgf.ParsePoint(move, board.Size)
+ moves = append(moves, []string{color, fmt.Sprintf("(%d,%d)", x, y)})
+ }
+ }
+
+ initialStones := make([][]string, 0)
+
+ _, present := root.GetValue("HA")
+ if present {
+ log.Fatal("TODO")
+ }
+
+ request := Request{
+ Komi: root.RootKomi(),
+ Rules: strings.ToLower(rules),
+ XSize: board.Size,
+ YSize: board.Size,
+ Moves: moves,
+ InitialStones: initialStones,
+ }
+
+ respch, err := g.Katago.request(request)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ log.Print("Waiting for response")
+ resp := <-respch
+ log.Print("Got response ", resp)
}
// Draws the goban in the area
@@ -94,7 +153,6 @@ func (g *App) LayoutGoban(gtx layout.Context) layout.Dimensions {
case key.Event:
{
e := ev.(key.Event)
- log.Print(e)
if e.State == key.Press {
switch e.Name {
case key.NameDownArrow:
@@ -135,6 +193,7 @@ func (g *App) LayoutGoban(gtx layout.Context) layout.Dimensions {
}.Op())
// Now draw the stones
+ // TODO: images ?
for x := 0; x < board.Size; x++ {
for y := 0; y < board.Size; y++ {
if board.State[x][y] != sgf.EMPTY {
@@ -168,15 +227,8 @@ func (g *App) LayoutGoban(gtx layout.Context) layout.Dimensions {
return layout.Dimensions{Size: image.Pt(size, size)}
}
-func run(window *app.Window) error {
- // theme := material.NewTheme()
+func run(window *app.Window, goban *App) error {
var ops op.Ops
- th := material.NewTheme()
-
- goban := App{
- Editor: material.Editor(th, new(widget.Editor), ""),
- }
- goban.SetNode(sgf.LoadArgOrQuit(1))
for {
switch e := window.Event().(type) {
@@ -200,14 +252,28 @@ func run(window *app.Window) error {
}
func main() {
+ th := material.NewTheme()
+ goban := new(App)
+
+ // Start KataGo
+ kat, err := newKatago()
+ if err != nil {
+ log.Fatal(err)
+ }
+ goban.Katago = kat
+
+ goban.Editor = material.Editor(th, new(widget.Editor), "")
+ goban.SetNode(sgf.LoadArgOrQuit(1))
+
go func() {
window := new(app.Window)
window.Option(app.MinSize(unit.Dp(1000), unit.Dp(700)))
- err := run(window)
+ err := run(window, goban)
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
+
app.Main()
}