package client import ( "fmt" "math" "math/rand" "bitbucket.org/hackerbots/server" "bitbucket.org/hackerbots/vector" ) var Verbose bool = false // 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(bot *server.Robot, bs *server.Boardstate) 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: 1000, safeDistance: 50, } } // Recv is our implementation of receiving a server.Boardstate from the server func (p *SimplePlayer) Update(bot *server.Robot, bs *server.Boardstate) server.Instruction{ p.me = *bot p.speed = 1000 if p.me.Health <= 0{ return server.Instruction{} } p.recon(bs) p.navigate() probe_point := p.me.Position.Add(p.me.Heading.Scale(p.safeDistance)) if Verbose { fmt.Printf("PROBE SENT: %v\n",probe_point) } return server.Instruction{ MoveTo: p.moveto, TargetSpeed: &p.speed, FireAt: p.fireat, Probe: &probe_point, } } func (p *SimplePlayer) navigate() { if Verbose { fmt.Printf("%v S:%v H:%v TS:%v\n\tX:%v Y:%v\n\tHX:%v HY:%v\n", p.me.Name, p.me.Speed, p.me.Health, p.me.TargetSpeed, p.me.Position.X, p.me.Position.Y, p.me.Heading.X, p.me.Heading.Y) if p.me.MoveTo != nil { fmt.Printf("\tTX:%v TY:%v\n", p.me.MoveTo.X, p.me.MoveTo.Y) } } // if !p.probe(p.me.Position.Add(p.me.Heading.Scale(p.safeDistance))) { // if !p.probe(*p.moveto) { // p.moveto = p.randomDirectionDrift(p.moveto, 20) // // p.speed = p.maxSpeed // fmt.Printf("Obstacle?\n") // return // } // } if p.me.ProbeResult != nil { p.moveto = p.randomDirectionDrift(&p.me.Position, 100) p.speed = -20 if Verbose { fmt.Printf("Probe %v\n", p.me.ProbeResult) } return } if p.me.Collision != nil { p.moveto = p.randomDirectionDrift(&p.me.Position, 100) p.speed = -20 if Verbose { fmt.Printf("Hit!\n") } return } if p.moveto == nil { p.moveto = p.randomDirection() // p.speed = p.maxSpeed if Verbose { fmt.Printf("Start\n") } return } togo := p.me.Position.Sub(*p.moveto).Mag() if togo < p.safeDistance+5 { p.moveto = p.randomDirection() // p.speed = p.maxSpeed if Verbose { fmt.Printf("New Dest\n") } return } } func (p *SimplePlayer) recon(bs *server.Boardstate) { // 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 } } func (p *SimplePlayer) randomDirectionDrift(start *vector.Point2d, drift float64) *vector.Point2d { for { pt := vector.Vector2d{ X: start.X - drift + rand.Float64() * drift * 2, Y: start.Y - drift + rand.Float64() * drift * 2, }.ToPoint() if pt.X > 0 && pt.X < p.width && pt.Y > 0 && pt.Y < p.height { return &pt } } } 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 }