github.com/gorgonia/agogo@v0.1.1/internal/gtp/gtp.go (about) 1 package gtp 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 8 "github.com/gorgonia/agogo/game" 9 "github.com/pkg/errors" 10 ) 11 12 var known_commands = []string{ 13 "protocol_version", 14 "name", 15 "version", 16 "known_command", 17 "list_commands", 18 "quit", 19 20 // setup 21 "boardsize", 22 "clear_board", 23 "komi", 24 "fixed_handicap", 25 "place_free_handicap", 26 "set_free_handicap", 27 28 // play 29 "play", 30 "genmove", 31 "undo", 32 } 33 34 type Engine struct { 35 g game.State 36 37 known map[string]Command 38 39 ch chan string 40 ret chan string 41 42 Generate func(g game.State) game.PlayerMove 43 New func(m, n int) game.State 44 name, version string 45 } 46 47 func New(g game.State, name, version string, known map[string]Command) *Engine { 48 if known == nil { 49 known = StandardLib() 50 } 51 return &Engine{ 52 g: g, 53 known: known, 54 name: name, 55 version: version, 56 } 57 } 58 59 func (e *Engine) Start() (input, output chan string) { 60 e.ch = make(chan string) 61 e.ret = make(chan string) 62 go e.start() 63 return e.ch, e.ret 64 } 65 66 func (e *Engine) State() game.State { return e.g } 67 68 func (e *Engine) start() { 69 for cmd := range e.ch { 70 id, x, args, err := e.parse(cmd) 71 if x == nil && err == nil { 72 continue // 73 } 74 if err != nil { 75 e.ret <- handleErr(id, err) 76 continue 77 } 78 id, result, err := x.Do(id, args, e) 79 e.ret <- handleResult(id, result, err) 80 } 81 } 82 83 // refer to this 84 // https://www.lysator.liu.se/%7Egunnar/gtp/gtp2-spec-draft2/gtp2-spec.html#SECTION00030000000000000000 85 func (e *Engine) parse(cmd string) (id int, x Command, args []string, err error) { 86 cmd = preprocess(cmd) 87 tokens := strings.Fields(cmd) 88 if id, err = strconv.Atoi(tokens[0]); err == nil { 89 // we've consumed ID 90 tokens = tokens[1:] 91 } else { 92 // set err to nil because ID is optional 93 err = nil 94 id = -1 95 } 96 97 if len(tokens) == 0 { 98 return id, nil, nil, nil // GNUGo some how does nothing when there are no tokens left. An ID may be passed in but it'll be ignored 99 } 100 101 var ok bool 102 if x, ok = e.known[tokens[0]]; !ok { 103 return id, nil, nil, errors.Errorf("Unknown command %q", tokens[0]) 104 } 105 if len(tokens) > 1 { 106 args = tokens[1:] 107 } 108 return 109 } 110 111 func preprocess(a string) string { 112 return strings.ToLower(strings.TrimSpace(a)) 113 } 114 115 func sqrt(a int) int { 116 if a == 0 || a == 1 { 117 return a 118 } 119 start := 1 120 end := a / 2 121 var retVal int 122 for start <= end { 123 mid := (start + end) / 2 124 sq := mid * mid 125 if sq == a { 126 return mid 127 } 128 if sq < a { 129 start = mid + 1 130 retVal = mid 131 } else { 132 end = mid - 1 133 } 134 } 135 return retVal 136 } 137 138 func handleErr(id int, err error) string { 139 if id != -1 { 140 return fmt.Sprintf("? %d %v\n\n", id, err) 141 } 142 return fmt.Sprintf("? %v\n\n", err) 143 } 144 145 func handleResult(id int, result string, err error) string { 146 if err != nil { 147 return handleErr(id, err) 148 } 149 150 if id != -1 { 151 return fmt.Sprintf("= %d %v\n\n", id, result) 152 } 153 return fmt.Sprintf("= %v\n\n", result) 154 }