package client import ( "encoding/gob" "encoding/json" "errors" "fmt" "log" "golang.org/x/net/websocket" "hackerbots.us/server" ) func connect(server string, port int) (*websocket.Conn, error) { origin := "http://localhost/" url := fmt.Sprintf("ws://%s:%d/ws/", server, port) return websocket.Dial(url, "", origin) } // Client keeps track of connection to server and has two interesting methods: // Negociate and Play. Users of this struct will likely use most everything as // is while defining their own Player to specify desired game play behavior. type Client struct { ForceJSON bool GameId string Name string Port int Server string StatsReq server.StatsRequest Verbose bool Player Player Game server.GameParam boardstate *server.Boardstate enc encoder dec decoder ws *websocket.Conn } type encoder interface { Encode(v interface{}) error } type decoder interface { Decode(v interface{}) error } // Negociate runs through the hackerbots negociation protocol. func (c *Client) Negociate(clientType string) (err error) { if c.Verbose { log.Printf("%s: trying to connect to game '%s'", c.Name, c.GameId) } c.ws, err = connect(c.Server, c.Port) if err != nil { return errors.New(fmt.Sprintf("connection failure: %s", err)) } err = websocket.JSON.Send(c.ws, struct { Id string `json:"id"` }{ c.GameId, }) if err != nil { return err } var idreq struct { Type string `json:"type"` PlayerId string `json:"id"` } err = websocket.JSON.Receive(c.ws, &idreq) if err != nil || idreq.Type == "failure" { return errors.New(fmt.Sprintf("failure: %+v", idreq)) } if c.Verbose { log.Printf("%s: idreq: %+v", c.Name, idreq) } err = websocket.JSON.Send(c.ws, struct { Type string `json:"type"` Name string `json:"name"` Useragent string `json:"useragent"` }{ Name: c.Name, Useragent: "gobot", Type: clientType, }) if err != nil { return err } supportedEncs := []string{"bson", "json", "gob"} if c.ForceJSON { supportedEncs = []string{"json"} } err = websocket.JSON.Send(c.ws, supportedEncs) if err != nil { return errors.New(fmt.Sprintf("failure: %+v", err)) } err = websocket.JSON.Receive(c.ws, &c.Game) if c.Game.Type != "gameparam" { return errors.New("didn't receive a good gameparam") } if c.Verbose { log.Printf("%s: game parameters: %+v", c.Name, c.Game) } if c.Game.Encoding == "json" { c.enc = json.NewEncoder(c.ws) c.dec = json.NewDecoder(c.ws) } else { c.enc = gob.NewEncoder(c.ws) c.dec = gob.NewDecoder(c.ws) } switch clientType { case "robot": conf := server.ClientConfig{ ID: c.GameId, Stats: map[string]server.StatsRequest{ c.Name: c.StatsReq, }, } if c.Verbose { log.Printf("%s: client config: %+v", c.Name, conf) } err = websocket.JSON.Send(c.ws, conf) var handshake struct { Id string `json:"id"` Success bool `json:"success"` Type string `json:"type"` server.Failure } websocket.JSON.Receive(c.ws, &handshake) if !handshake.Success { return errors.New(handshake.Reason) } if c.Verbose { log.Printf("%s: handshake: %+v", c.Name, handshake) } // we don't do anything useful with dstats, but could be interesting to // pass along to the player? dstats := struct { Stats map[string]server.Stats `json:"stats"` Type string `json:"type"` }{} err = websocket.JSON.Receive(c.ws, &dstats) if err != nil { return err } case "spectator": } return nil } // Play contains the main game run loop. It gets a server.Boardstate from the // server, and passes this along to the embedded Player. Following this it // sends the server the Player's instruction. func (c *Client) Play() error { log.Printf("%s: starting loop", c.Name) bs := &server.Boardstate{} var err error for { err = c.dec.Decode(bs) if err != nil { return errors.New(fmt.Sprintf("%s: Connection likely lost: %s", c.Name, err)) } err = c.enc.Encode(c.Player.Update(bs)) if err != nil { return err } } }