package botclient import ( "encoding/gob" "encoding/json" "errors" "fmt" "log" "math" "math/rand" "sync" "bitbucket.org/hackerbots/botserv" "bitbucket.org/hackerbots/vector" "code.google.com/p/go.net/websocket" ) const maxSpeed = 100 const safeDistance = 40 const maxSearchIterations = 20 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) } type Robot struct { verbose bool forceJSON bool Server string Port int ws *websocket.Conn game botserv.GameParam playerId string Name string GameName string StatsReq botserv.StatsRequest statsCalculated botserv.Stats speed float32 moveto *govector.Point2d fireat *govector.Point2d boardstate botserv.Boardstate me botserv.Robot KnownObstacles map[string]botserv.Obstacle nearestEnemy *botserv.OtherRobot // TODO: don't know if I like how this is done ... I would rather send // a signal over a chanel Wg *sync.WaitGroup } type encoder interface { Encode(v interface{}) error } type decoder interface { Decode(v interface{}) error } func (r *Robot) negociate() (err error) { if r.verbose { log.Printf("%s: trying to connect to game '%s'", r.Name, r.GameName) } r.ws, err = connect(r.Server, r.Port) if err != nil { return errors.New(fmt.Sprintf("connection failure: %s", err)) } err = websocket.JSON.Send(r.ws, struct { Id string `json:"id"` }{ r.GameName, }) if err != nil { return err } var idreq struct { Type string `json:"type"` PlayerId string `json:"id"` } err = websocket.JSON.Receive(r.ws, &idreq) if err != nil || idreq.Type == "failure" { return errors.New(fmt.Sprintf("failure: %+v", idreq)) } if r.verbose { log.Printf("%s: idreq: %+v", r.Name, idreq) } err = websocket.JSON.Send(r.ws, struct { Type string `json:"type"` Name string `json:"name"` Useragent string `json:"useragent"` }{ Name: r.Name, Useragent: "gobot", Type: "robot", }) if err != nil { return err } supportedEncs := []string{"bson", "json", "gob"} if r.forceJSON { supportedEncs = []string{"json"} } err = websocket.JSON.Send(r.ws, supportedEncs) if err != nil { return errors.New(fmt.Sprintf("failure: %+v", err)) } err = websocket.JSON.Receive(r.ws, &r.game) if r.game.Type != "gameparam" { return errors.New("didn't receive a good gameparam") } if r.verbose { log.Printf("%s: game parameters: %+v", r.Name, r.game) } conf := botserv.ClientConfig{ ID: r.GameName, Stats: map[string]botserv.StatsRequest{ r.Name: r.StatsReq, }, } err = websocket.JSON.Send(r.ws, conf) var handshake struct { Id string `json:"id"` Success bool `json:"success"` Type string `json:"type"` botserv.Failure } websocket.JSON.Receive(r.ws, &handshake) if !handshake.Success { return errors.New(handshake.Reason) } r.playerId = handshake.Id if r.verbose { log.Printf("%s: handshake: %+v", r.Name, handshake) } dstats := struct { Stats map[string]botserv.Stats `json:"stats"` Type string `json:"type"` }{} err = websocket.JSON.Receive(r.ws, &dstats) if err != nil { return err } log.Printf("%s: recv stats", r.Name) // this player only ever has one robot, so we're just picking off our own // stats _, ok := dstats.Stats[r.Name] if !ok { return errors.New("my name not found in stats map") } r.statsCalculated = dstats.Stats[r.Name] return nil } func (r *Robot) Play() { defer r.Wg.Done() var err error err = r.negociate() if err != nil { log.Printf("%s: failed to negociate: %s", r.Name, err) return } log.Printf("%s: starting loop", r.Name) var enc encoder var dec decoder if r.game.Encoding == "json" { enc = json.NewEncoder(r.ws) dec = json.NewDecoder(r.ws) } else { enc = gob.NewEncoder(r.ws) dec = gob.NewDecoder(r.ws) } for { r.speed = float32(maxSpeed) if r.verbose { log.Printf("%s: about to wait on boardstate", r.Name) } err = dec.Decode(&r.boardstate) if err != nil { log.Printf("%s: Connection lost", r.Name) return } if r.verbose { log.Printf("%s: one recv boardstate", r.Name) } // TODO: I need a truly-verbose flag? // if r.verbose { // log.Printf("\n\n%#v\n\n", r.boardstate) // } r.recon() if len(r.boardstate.MyRobots) > 0 { r.me = r.boardstate.MyRobots[0] } else { continue } if r.verbose { log.Printf("%s before: %+v", r.Name, r.moveto) } r.navigate() if r.verbose { log.Printf("%s after: %+v", r.Name, r.moveto) } instruction := map[string]botserv.Instruction{ r.me.Id: { MoveTo: r.moveto, TargetSpeed: &r.speed, FireAt: r.fireat, }, } err = enc.Encode(instruction) if err != nil { log.Println(err) return } } } func (r *Robot) recon() { for _, o := range r.boardstate.Objects { obj := MiniObstacle(o) if _, ok := r.KnownObstacles[obj.id()]; !ok { r.KnownObstacles[obj.id()] = obj.toObstacle() } } // simplest shooting strategy ... need to do the following: // not shoot through buildings // shoot at where the robot will be, not where it was. r.nearestEnemy = nil r.fireat = nil closest := float32(math.Inf(1)) for _, enemy := range r.boardstate.OtherRobots { dist := r.me.Position.Sub(enemy.Position).Mag() if dist < closest && dist > safeDistance { r.nearestEnemy = &enemy } } if r.nearestEnemy != nil { point := r.nearestEnemy.Position.Add(r.nearestEnemy.Heading.Scale(safeDistance)) r.fireat = &point } } func (r *Robot) randomDirection() *govector.Point2d { p := govector.Vector2d{ X: rand.Float32() * r.game.BoardSize.Width, Y: rand.Float32() * r.game.BoardSize.Height, }.ToPoint() return &p } func (r *Robot) probe(destination govector.Point2d) bool { // XXX: make test for this for _, v := range r.KnownObstacles { collided, _, _ := govector.RectIntersection( v.Bounds, r.me.Position, destination.Sub(r.me.Position), ) if collided { return false } } return true } func (r *Robot) navigate() { if r.moveto == nil { if r.verbose { log.Printf("%s: nil", r.Name) } r.moveto = r.randomDirection() } togo := r.me.Position.Sub(*r.moveto).Mag() if togo < safeDistance+5 { if r.verbose { log.Printf("%s got to destination", r.Name) } r.moveto = r.randomDirection() return } if !r.probe(r.me.Position.Add(r.me.Heading.Scale(safeDistance))) { if r.verbose { log.Printf("%s going to run into something", r.Name) } r.speed = 0 if !r.probe(*r.moveto) { if r.verbose { log.Printf("%s unsafe to move, choose new direction", r.Name) } r.moveto = r.randomDirection() return } } if r.me.Collision != nil { // XXX: I am being told I am here ... if r.verbose { log.Printf("%s apparent collision: %#v", r.Name, r.me.Collision) } r.moveto = r.randomDirection() r.speed = 0 return } }