igoui

Log | Files | Refs

commit d9952afc99f22175419f413c2370ce4fc2135421
parent 749eb9f69ad3e4f81fb0f960e7eda980b5e3a506
Author: Thomas Vigouroux <me@vigoux.eu>
Date:   Fri, 26 Apr 2024 15:39:39 +0200

feat: start katago integration

Diffstat:
Akatago.go | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmain.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() }