main.go (5935B)
1 package main 2 3 import ( 4 "image" 5 "image/color" 6 "strings" 7 // "image/png" 8 "fmt" 9 "log" 10 "os" 11 12 // UI generation 13 "gioui.org/app" 14 "gioui.org/f32" 15 "gioui.org/io/event" 16 "gioui.org/io/key" 17 "gioui.org/io/pointer" 18 "gioui.org/layout" 19 "gioui.org/op" 20 "gioui.org/op/clip" 21 "gioui.org/op/paint" 22 "gioui.org/unit" 23 "gioui.org/widget" 24 "gioui.org/widget/material" 25 // "gioui.org/text" 26 // "gioui.org/widget/material" 27 28 // SGF manipulation 29 "github.com/rooklift/sgf" 30 ) 31 32 type App struct { 33 Node *sgf.Node 34 Katago *Katago 35 Editor material.EditorStyle 36 } 37 38 func (g *App) SetNode(node *sgf.Node) { 39 if g.Node != nil { 40 newcomment := g.Editor.Editor.Text() 41 _, present := g.Node.GetValue("C") 42 if newcomment != "" || present { 43 g.Node.SetValue("C", newcomment) 44 } 45 } 46 g.Node = node 47 48 val, ok := node.GetValue("C") 49 if ok { 50 g.Editor.Editor.SetText(val) 51 } else { 52 g.Editor.Editor.SetText("") 53 } 54 55 go g.AnalyzeCurrent() 56 } 57 58 func (g *App) AnalyzeCurrent() { 59 root := g.Node.GetRoot() 60 board := root.Board() 61 rules, _ := root.GetValue("RU") 62 63 line := g.Node.GetLine() 64 65 moves := make([][]string, 0) 66 for _, inner := range line { 67 move, iswhite := inner.GetValue("W") 68 var color string 69 var isblack bool 70 if iswhite { 71 color = "W" 72 } else if move, isblack = inner.GetValue("B"); isblack { 73 color = "B" 74 } 75 76 if iswhite || isblack { 77 x, y, _ := sgf.ParsePoint(move, board.Size) 78 moves = append(moves, []string{color, fmt.Sprintf("(%d,%d)", x, y)}) 79 } 80 } 81 82 initialStones := make([][]string, 0) 83 84 _, present := root.GetValue("HA") 85 if present { 86 log.Fatal("TODO") 87 } 88 89 request := Request{ 90 Komi: root.RootKomi(), 91 Rules: strings.ToLower(rules), 92 XSize: board.Size, 93 YSize: board.Size, 94 Moves: moves, 95 InitialStones: initialStones, 96 } 97 98 respch, err := g.Katago.request(request) 99 if err != nil { 100 log.Fatal(err) 101 } 102 103 log.Print("Waiting for response") 104 resp := <-respch 105 log.Print("Got response ", resp) 106 } 107 108 // Draws the goban in the area 109 // Also handles navigation of the game tree 110 func (g *App) LayoutGoban(gtx layout.Context) layout.Dimensions { 111 size := min(gtx.Constraints.Max.X, gtx.Constraints.Max.Y) 112 const padding = 2 113 black := color.NRGBA{A: 0xFF} 114 white := color.NRGBA{A: 0xFF, R: 0xFF, G: 0xFF, B: 0xFF} 115 116 // Handle inputs within the goban area 117 { 118 defer clip.Rect(image.Rect(0, 0, size, size)).Push(gtx.Ops).Pop() 119 offset := float32(size) / float32(g.Node.Board().Size) 120 121 event.Op(gtx.Ops, g) 122 123 for { 124 ev, ok := gtx.Event(pointer.Filter{ 125 Target: g, 126 Kinds: pointer.Press, 127 }, key.Filter{ 128 Name: key.NameDownArrow, 129 }, key.Filter{ 130 Name: key.NameUpArrow, 131 }) 132 if !ok { 133 break 134 } 135 136 var newnode *sgf.Node 137 switch ev.(type) { 138 case pointer.Event: 139 { 140 e := ev.(pointer.Event) 141 switch e.Kind { 142 case pointer.Press: 143 xpos := int(e.Position.X / offset) 144 ypos := int(e.Position.Y / offset) 145 var err error 146 newnode, err = g.Node.Play(sgf.Point(xpos, ypos)) 147 if err != nil { 148 newnode = nil 149 } 150 newnode.MakeMainLine() 151 } 152 } 153 case key.Event: 154 { 155 e := ev.(key.Event) 156 if e.State == key.Press { 157 switch e.Name { 158 case key.NameDownArrow: 159 newnode = g.Node.MainChild() 160 case key.NameUpArrow: 161 newnode = g.Node.Parent() 162 } 163 } 164 } 165 } 166 if newnode != nil { 167 g.SetNode(newnode) 168 } 169 } 170 } 171 172 board := g.Node.Board() 173 offset := float32(size) / float32(board.Size) 174 175 // Draw the frame 176 var path clip.Path 177 path.Begin(gtx.Ops) 178 foffset := offset / 2 179 for i := 0; i < board.Size; i++ { 180 ioffset := float32(offset)*float32(i) + foffset 181 // Line from left to right 182 path.MoveTo(f32.Pt(ioffset, foffset)) 183 path.LineTo(f32.Pt(ioffset, float32(size)-foffset)) 184 185 // Line from top to bottom 186 path.MoveTo(f32.Pt(foffset, ioffset)) 187 path.LineTo(f32.Pt(float32(size)-foffset, ioffset)) 188 } 189 path.Close() 190 paint.FillShape(gtx.Ops, black, clip.Stroke{ 191 Path: path.End(), 192 Width: 4, 193 }.Op()) 194 195 // Now draw the stones 196 // TODO: images ? 197 for x := 0; x < board.Size; x++ { 198 for y := 0; y < board.Size; y++ { 199 if board.State[x][y] != sgf.EMPTY { 200 scolor := board.State[x][y] 201 fx := float32(x) 202 fy := float32(y) 203 rect := image.Rect(int(fx*offset+padding/2), int(fy*offset+padding/2), int((fx+1)*offset-padding/2), int((fy+1)*offset-padding/2)) 204 elipse := clip.Ellipse(rect) 205 206 var strokecolor color.NRGBA 207 var fillcolor color.NRGBA 208 209 if scolor == sgf.BLACK { 210 strokecolor = white 211 fillcolor = black 212 } else { 213 strokecolor = black 214 fillcolor = white 215 } 216 217 // Now that we have the rectangle, put the stone 218 paint.FillShape(gtx.Ops, fillcolor, elipse.Op(gtx.Ops)) 219 paint.FillShape(gtx.Ops, strokecolor, clip.Stroke{ 220 Path: elipse.Path(gtx.Ops), 221 Width: padding, 222 }.Op()) 223 } 224 } 225 } 226 227 return layout.Dimensions{Size: image.Pt(size, size)} 228 } 229 230 func run(window *app.Window, goban *App) error { 231 var ops op.Ops 232 233 for { 234 switch e := window.Event().(type) { 235 case app.DestroyEvent: 236 return e.Err 237 case app.FrameEvent: 238 // This graphics context is used for managing the rendering state. 239 gtx := app.NewContext(&ops, e) 240 241 layout.Flex{}.Layout(gtx, 242 layout.Rigid(func(gtx layout.Context) layout.Dimensions { 243 return layout.UniformInset(unit.Dp(30)).Layout(gtx, goban.LayoutGoban) 244 }), 245 layout.Flexed(0.5, goban.Editor.Layout), 246 ) 247 248 // Pass the drawing operations to the GPU. 249 e.Frame(gtx.Ops) 250 } 251 } 252 } 253 254 func main() { 255 th := material.NewTheme() 256 goban := new(App) 257 258 // Start KataGo 259 kat, err := newKatago() 260 if err != nil { 261 log.Fatal(err) 262 } 263 goban.Katago = kat 264 265 goban.Editor = material.Editor(th, new(widget.Editor), "") 266 goban.SetNode(sgf.LoadArgOrQuit(1)) 267 268 go func() { 269 window := new(app.Window) 270 window.Option(app.MinSize(unit.Dp(1000), unit.Dp(700))) 271 err := run(window, goban) 272 if err != nil { 273 log.Fatal(err) 274 } 275 os.Exit(0) 276 }() 277 278 app.Main() 279 }