package client import ( "encoding/gob" "encoding/json" "errors" "fmt" "log" hbserver "bitbucket.org/hackerbots/server" "code.google.com/p/go.net/websocket" ) 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 hbserver.StatsRequest Verbose bool Player Player Game hbserver.GameParam boardstate hbserver.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() (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: "robot", }) 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) } conf := hbserver.ClientConfig{ ID: c.GameId, Stats: map[string]hbserver.StatsRequest{ c.Name: c.StatsReq, }, } err = websocket.JSON.Send(c.ws, conf) var handshake struct { Id string `json:"id"` Success bool `json:"success"` Type string `json:"type"` hbserver.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]hbserver.Stats `json:"stats"` Type string `json:"type"` }{} err = websocket.JSON.Receive(c.ws, &dstats) if err != nil { return err } return nil } // Play contains the main game run loop. It gets a hbserver.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) var err error for { err = c.dec.Decode(&c.boardstate) if err != nil { return errors.New(fmt.Sprintf("%s: Connection likely lost: %s", c.Name, err)) } c.Player.Recv(&c.boardstate) instruction := c.Player.Instruction() err = c.enc.Encode(instruction) if err != nil { return err } } return nil }