package client import ( "math" "math/rand" "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 { Update(bs *server.Boardstate) map[string]server.Instruction } // SimplePlayer is our default player and stands as a starting point for your // own Player implementations. type SimplePlayer struct { me server.Robot width, height float64 knownObstacles map[string]server.Obstacle nearestEnemy *server.OtherRobot fireat *vector.Point2d moveto *vector.Point2d speed float64 maxSpeed float64 safeDistance float64 } // NewSimplePlayer simply returns a populated, usable *SimplePlayer func NewSimplePlayer(width, height float64) *SimplePlayer { return &SimplePlayer{ knownObstacles: make(map[string]server.Obstacle), width: width, height: height, maxSpeed: 100, safeDistance: 40, } } // Update is our implementation of recieving and processing a server.Boardstate // from the server func (p *SimplePlayer) Update(bs *server.Boardstate) map[string]server.Instruction { instructions := make(map[string]server.Instruction) for _, bot := range bs.MyRobots { p.me = bot p.speed = 1000 if p.me.Health <= 0 { continue } p.recon(bs) p.navigate() probe_point := p.me.Position.Add(p.me.Heading.Scale(p.safeDistance)) instructions[bot.Id] = server.Instruction{ MoveTo: p.moveto, TargetSpeed: &p.speed, FireAt: p.fireat, Probe: &probe_point, } } return instructions } 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 *server.Boardstate) { // XXX: need to keep track of seen objects .. // 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 := 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]server.Instruction { return map[string]server.Instruction{ p.me.Id: { MoveTo: p.moveto, TargetSpeed: &p.speed, FireAt: p.fireat, }, } } func (p *SimplePlayer) randomDirection() *vector.Point2d { pt := vector.Vector2d{ X: rand.Float64() * p.width, Y: rand.Float64() * 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 }