package main import ( "encoding/gob" "encoding/json" "errors" "fmt" "log" "math" "math/rand" "sync" "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 { server string port int ws *websocket.Conn game GameParam playerId string name string statsReq StatsRequest statsCalculated Stats speed float32 moveto *govector.Point2d fireat *govector.Point2d boardstate Boardstate me Robot knownObstacles map[string]Obstacle nearestEnemy *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 *verbose { log.Printf("%s: trying to connect to game '%s'", r.name, r.game.Name) } 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.game.Name, }) 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 *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"} 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 *verbose { log.Printf("%s: game parameters: %+v", r.name, r.game) } conf := ClientConfig{ ID: r.game.Name, Stats: map[string]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"` Failure } websocket.JSON.Receive(r.ws, &handshake) if !handshake.Success { return errors.New(handshake.Reason) } r.playerId = handshake.Id if *verbose { log.Printf("%s: handshake: %+v", r.name, handshake) } dstats := struct { Stats map[string]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 *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 *verbose { log.Printf("%s: one recv boardstate", r.name) } // TODO: I need a truly-verbose flag? // if *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 *verbose { log.Printf("%s before: %+v", r.name, r.moveto) } r.navigate() if *verbose { log.Printf("%s after: %+v", r.name, r.moveto) } instruction := map[string]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.Obstacles { if _, ok := r.knownObstacles[o.id()]; !ok { r.knownObstacles[o.id()] = o.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.boardstate.Reset { // XXX We actually *want* to get here, but never do .. panic("reset") r.moveto = r.randomDirection() } if r.moveto == nil { if *verbose { log.Printf("%s: nil", r.name) } r.moveto = r.randomDirection() } togo := r.me.Position.Sub(*r.moveto).Mag() if togo < safeDistance+5 { if *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 *verbose { log.Printf("%s going to run into something", r.name) } r.speed = 0 if !r.probe(*r.moveto) { if *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 *verbose { log.Printf("%s apparent collision: %#v", r.name, r.me.Collision) } r.moveto = r.randomDirection() r.speed = 0 return } }