package main import ( "fmt" "io" "log" "net" "net/http" "net/url" "strconv" "strings" "time" "code.rocket9labs.com/tslocum/bei" "code.rocket9labs.com/tslocum/bgammon" ) type BEIClient struct { address string conn net.Conn connected bool terminated bool count int replayDir string primary bool quiet bool started time.Time } func NewBEIClient(address string, count int, replayDir string, primary bool, quiet bool) *BEIClient { c := &BEIClient{ address: address, count: count, replayDir: replayDir, primary: primary, quiet: quiet, started: time.Now(), } return c } func (c *BEIClient) REST() bool { return strings.HasPrefix(c.address, "http") } func (c *BEIClient) restRequest(command string, vars map[string]string) (interface{}, error) { if !c.REST() { return nil, fmt.Errorf("client is not using a REST connection") } requestURL := c.address + "/" + command if len(vars) > 0 { var i int for key, value := range vars { if i > 0 { requestURL += "&" } requestURL += key + "=" + url.QueryEscape(value) i++ } } const attempts = 5 var err error for i := 0; i < attempts; i++ { if Debug > 0 { log.Printf("REQUEST %s", requestURL) } var r *http.Response r, err = httpClient.Get(requestURL) if err != nil { continue } defer r.Body.Close() body, err := io.ReadAll(r.Body) if err != nil { continue } if len(body) == 0 { continue } return bei.DecodeEvent(body) } return nil, err } func (c *BEIClient) Connect() error { if c.terminated { return nil } if c.REST() { ev, err := c.restRequest(bei.CommandBEI, nil) if err != nil { return err } if _, ok := ev.(*bei.EventOkBEI); !ok { return fmt.Errorf("unexpected reply from server") } c.connected = true return nil } conn, err := net.Dial("tcp", c.address) if err != nil { return err } c.connected = true c.conn = conn c.conn.Write([]byte("bei\n")) buf, err := c.readLine() if err != nil { return err } response, err := bei.DecodeEvent(buf) if err != nil { return err } if _, ok := response.(*bei.EventOkBEI); !ok { return fmt.Errorf("unexpected reply from server") } return nil } func (c *BEIClient) readLine() ([]byte, error) { var buf []byte b := make([]byte, 1) for { n, err := c.conn.Read(b) if err != nil { return nil, err } else if n != 1 { return nil, fmt.Errorf("zero read") } if b[0] == '\n' { return buf, nil } buf = append(buf, b[0]) } } func (c *BEIClient) Double(state []int8) (interface{}, error) { if c.terminated { return nil, nil } else if !c.connected { err := c.Connect() if err != nil { return nil, fmt.Errorf("failed to connect to engine at %s: %s", c.address, err) } } var stateBytes []byte for i, v := range state { if i > 0 { stateBytes = append(stateBytes, ',') } stateBytes = append(stateBytes, []byte(strconv.Itoa(int(v)))...) } if c.REST() { return c.restRequest(bei.CommandDouble, map[string]string{ "state": string(stateBytes), }) } c.conn.Write([]byte(fmt.Sprintf("double %s\n", stateBytes))) buf, err := c.readLine() if err != nil { return nil, err } return bei.DecodeEvent(buf) } func (c *BEIClient) Move(state []int8) (interface{}, error) { if c.terminated { return nil, nil } else if !c.connected { err := c.Connect() if err != nil { return nil, fmt.Errorf("failed to connect to engine at %s: %s", c.address, err) } } var stateBytes []byte for i, v := range state { if i > 0 { stateBytes = append(stateBytes, ',') } stateBytes = append(stateBytes, []byte(strconv.Itoa(int(v)))...) } if c.REST() { return c.restRequest(bei.CommandMove, map[string]string{ "state": string(stateBytes), }) } c.conn.Write([]byte(fmt.Sprintf("move %s\n", stateBytes))) buf, err := c.readLine() if err != nil { return nil, err } return bei.DecodeEvent(buf) } func (c *BEIClient) Choose(state []int8) (interface{}, error) { if c.terminated { return nil, nil } else if !c.connected { err := c.Connect() if err != nil { return nil, fmt.Errorf("failed to connect to engine at %s: %s", c.address, err) } } var stateBytes []byte for i, v := range state { if i > 0 { stateBytes = append(stateBytes, ',') } stateBytes = append(stateBytes, []byte(strconv.Itoa(int(v)))...) } if c.REST() { return c.restRequest(bei.CommandChoose, map[string]string{ "state": string(stateBytes), }) } c.conn.Write([]byte(fmt.Sprintf("choose %s\n", stateBytes))) buf, err := c.readLine() if err != nil { return nil, err } return bei.DecodeEvent(buf) } func beiState(game *bgammon.Game) []int8 { // TODO send turn numbers var state []int8 state = append(state, game.Board...) state = append(state, game.Roll1, game.Roll2, game.Roll3, 1, 1, game.DoubleValue, game.DoublePlayer, game.Player1.Points, game.Player2.Points, game.Points) entered1, entered2 := int8(1), int8(1) if game.Variant != bgammon.VariantBackgammon { if !game.Player1.Entered { entered1 = 0 } if !game.Player2.Entered { entered2 = 0 } } return append(state, 0, 0, 0, 0, game.Variant, entered1, entered2) }