package client import ( "encoding/gob" "encoding/json" "errors" "fmt" "golang.org/x/net/websocket" "hackerbots.us/server" ) func connect(addr string) (*websocket.Conn, error) { origin := "http://localhost/" url := fmt.Sprintf("%s/ws/", addr) return websocket.Dial(url, "", origin) } // Client keeps track of connection to server and has two interesting methods: // Negotiate 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 Server string Game server.GameParam boardstate *server.Boardstate enc encoder dec decoder ws *websocket.Conn // visualization members width, height float64 viewX, viewY int StateStream chan *server.Boardstate Die chan struct{} Player } type encoder interface { Encode(v interface{}) error } type decoder interface { Decode(v interface{}) error } // Negotiate runs through the hackerbots negociation protocol. func (c *Client) Negotiate(clientType string, player Player) (err error) { c.ws, err = connect(c.Server) 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)) } 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.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: player.GetStats(), } 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) } // 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 } ids := make(map[string]string) for name, entry := range dstats.Stats { ids[entry.Id] = name } player.SetIDs(ids) case "spectator": } c.width = c.Game.BoardSize.Width c.height = c.Game.BoardSize.Height 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 { 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)) } select { case c.StateStream <- bs: default: } err = c.enc.Encode(c.Update(bs)) if err != nil { return err } } }