client/client.go

176 lines
3.9 KiB
Go

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
}