client/client.go

181 lines
3.8 KiB
Go

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
}
}
}