package client import ( "fmt" "math" "math/rand" hbserver "bitbucket.org/hackerbots/server" "bitbucket.org/hackerbots/vector" ) // Player is the interface that is implemented when specifying non-default // player behavior. // // The general case will be to implement a Player type that contains the magic // required to slay other robots quickly while staying alive for a long time. type Player interface { Recv(bs *hbserver.Boardstate) Instruction() map[string]hbserver.Instruction } // SimplePlayer is our default player and stands as a starting point for your // own Player implementations. type SimplePlayer struct { me hbserver.Robot width, height float32 knownObstacles map[string]hbserver.Obstacle nearestEnemy *hbserver.OtherRobot fireat *vector.Point2d moveto *vector.Point2d speed float32 maxSpeed float32 safeDistance float32 } // NewSimplePlayer simply returns a populated, usable *SimplePlayer func NewSimplePlayer(width, height float32) *SimplePlayer { return &SimplePlayer{ knownObstacles: make(map[string]hbserver.Obstacle), width: width, height: height, maxSpeed: 100, safeDistance: 40, } } // Recv is our implementation of receiving a hbserver.Boardstate from the server func (p *SimplePlayer) Recv(bs *hbserver.Boardstate) { p.speed = p.maxSpeed if len(bs.MyRobots) > 0 { p.me = bs.MyRobots[0] } else { return } p.recon(bs) p.navigate() } func (p *SimplePlayer) navigate() { if p.moveto == nil { p.moveto = p.randomDirection() } togo := p.me.Position.Sub(*p.moveto).Mag() if togo < p.safeDistance+5 { p.moveto = p.randomDirection() return } if !p.probe(p.me.Position.Add(p.me.Heading.Scale(p.safeDistance))) { p.speed = 0 if !p.probe(*p.moveto) { p.moveto = p.randomDirection() return } } if p.me.Collision != nil { p.moveto = p.randomDirection() p.speed = 0 return } } func (p *SimplePlayer) recon(bs *hbserver.Boardstate) { for _, o := range bs.Objects { obj := MiniObstacle(o) if _, ok := p.knownObstacles[obj.Id()]; !ok { p.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. p.nearestEnemy = nil p.fireat = nil closest := float32(math.Inf(1)) for _, enemy := range bs.OtherRobots { dist := p.me.Position.Sub(enemy.Position).Mag() if dist < closest && dist > p.safeDistance { p.nearestEnemy = &enemy } } if p.nearestEnemy != nil { point := p.nearestEnemy.Position.Add(p.nearestEnemy.Heading.Scale(p.safeDistance)) p.fireat = &point } } // Instruction is our default implementation of preparing a map of information // to be sent to server. func (p *SimplePlayer) Instruction() map[string]hbserver.Instruction { return map[string]hbserver.Instruction{ p.me.Id: { MoveTo: p.moveto, TargetSpeed: &p.speed, FireAt: p.fireat, }, } } func (p *SimplePlayer) randomDirection() *vector.Point2d { pt := vector.Vector2d{ X: rand.Float32() * p.width, Y: rand.Float32() * p.height, }.ToPoint() return &pt } func (p *SimplePlayer) probe(destination vector.Point2d) bool { // XXX: make test for this for _, v := range p.knownObstacles { collided, _, _ := vector.RectIntersection( v.Bounds, p.me.Position, destination.Sub(p.me.Position), ) if collided { return false } } return true } // MiniObstacle is a convenient way to encode/decode between the [4]int -> hbserver.Obstacle type MiniObstacle [4]int // id is used to calculate a key for use in maps func (mo *MiniObstacle) Id() string { return fmt.Sprintf( "%x%x%x%x", mo[0], mo[1], mo[2], mo[3], ) } func (mo MiniObstacle) String() string { return mo.Id() } // ToObstacle is where the conversion magic happens func (mo *MiniObstacle) ToObstacle() hbserver.Obstacle { return hbserver.Obstacle{ Bounds: vector.AABB2d{ A: vector.Point2d{float32(mo[0]), float32(mo[1])}, B: vector.Point2d{float32(mo[2]), float32(mo[3])}, }, } }