diff --git a/client.go b/client.go index aecf94b..ff8699d 100644 --- a/client.go +++ b/client.go @@ -52,7 +52,7 @@ type decoder interface { } // Negotiate runs through the hackerbots negociation protocol. -func (c *Client) Negotiate(clientType string) (err error) { +func (c *Client) Negotiate(clientType string, player Player) (err error) { c.ws, err = connect(c.Server, c.Port) if err != nil { return errors.New(fmt.Sprintf("connection failure: %s", err)) @@ -114,10 +114,8 @@ func (c *Client) Negotiate(clientType string) (err error) { switch clientType { case "robot": conf := server.ClientConfig{ - ID: c.GameId, - Stats: map[string]server.StatsRequest{ - c.Name: c.StatsReq, - }, + ID: c.GameId, + Stats: player.GetStats(), } err = websocket.JSON.Send(c.ws, conf) diff --git a/cmd/botspectate/main.go b/cmd/botspectate/main.go index a817cf6..562ea00 100644 --- a/cmd/botspectate/main.go +++ b/cmd/botspectate/main.go @@ -36,7 +36,7 @@ func main() { Player: client.Spectator{}, } var err error - err = c.Negotiate("spectator") + err = c.Negotiate("spectator", c.Player) if err != nil { fmt.Fprintf(os.Stderr, "%s: failed to negotiate: %s\n", c.Name, err) os.Exit(1) diff --git a/cmd/gobot/main.go b/cmd/gobot/main.go index 8447126..9539724 100644 --- a/cmd/gobot/main.go +++ b/cmd/gobot/main.go @@ -27,6 +27,7 @@ var serverHostname = flag.String("server", "localhost", "server hostname") var port = flag.Int("port", 8666, "server port") var botname = flag.String("name", "gobot", "the name that other players will see") var forceJSON = flag.Bool("json", false, "force json encoding") +var botType = flag.String("bot", "simple", "which Bot") func main() { rand.Seed(time.Now().UnixNano()) @@ -58,11 +59,17 @@ func main() { ForceJSON: *forceJSON, StateStream: make(chan *server.Boardstate), Die: make(chan struct{}), - - Player: client.NewSimplePlayer(800, 600), } + + switch *botType { + case "simple": + c.Player = client.NewSimplePlayer(800, 600, c.StatsReq) + case "fraserbot": + c.Player = client.NewFraserbot("Fraserbot", c.StatsReq) + } + var err error - err = c.Negotiate("robot") + err = c.Negotiate("robot", c.Player) if err != nil { fmt.Fprintf(os.Stderr, "%s: failed to negociate: %s\n", c.Name, err) os.Exit(1) diff --git a/fraserbot.go b/fraserbot.go new file mode 100644 index 0000000..032bddd --- /dev/null +++ b/fraserbot.go @@ -0,0 +1,96 @@ +package client + +import ( + "fmt" + "math/rand" + + "hackerbots.us/server" + "hackerbots.us/vector" +) + +// Fraserbot is a bad ass motherfucker, that will fuck SHIT UUUUUP +type Fraserbot struct { + me server.Robot + knownObstacles map[string]server.Obstacle + nearestEnemy *server.OtherRobot + fireat *vector.Point2d + moveto *vector.Point2d + speed float64 + stats server.StatsRequest + name string +} + +// NewFraserbot simply returns a populated, usable *Fraserbot +func NewFraserbot(name string, stats server.StatsRequest) *Fraserbot { + return &Fraserbot{ + knownObstacles: make(map[string]server.Obstacle), + stats: stats, + name: name, + } +} + +// GetStats returns a map with an entry for each robot the player will control +// containing the desired stats for that robot +func (p *Fraserbot) GetStats() map[string]server.StatsRequest { + s := make(map[string]server.StatsRequest) + + s[fmt.Sprintf("%v_MAIN", p.name)] = server.StatsRequest{ + Hp: 10, + Speed: 10, + Acceleration: 10, + ScannerRadius: 10, + TurnSpeed: 10, + FireRate: 10, + WeaponRadius: 10, + WeaponDamage: 10, + WeaponSpeed: 10, + } + + s[fmt.Sprintf("%v_Jr", p.name)] = server.StatsRequest{ + Hp: 10, + Speed: 10, + Acceleration: 10, + ScannerRadius: 10, + TurnSpeed: 10, + FireRate: 10, + WeaponRadius: 10, + WeaponDamage: 10, + WeaponSpeed: 10, + } + + return s +} + +// Update is our implementation of recieving and processing a server.Boardstate +// from the server +func (p *Fraserbot) 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.moveto == nil { + p.moveto = &p.me.Position + } + + if p.me.Position.Sub(*p.moveto).Mag() < 30 { + p.moveto = p.randomDirection() + } + + instructions[bot.Id] = server.Instruction{ + MoveTo: p.moveto, + TargetSpeed: &p.speed, + FireAt: p.fireat, + } + } + return instructions +} + +// randomDirection is a spot within 200 of the current position +func (p *Fraserbot) randomDirection() *vector.Point2d { + pt := vector.Vector2d{ + X: (rand.Float64() * 400) - 200 + p.me.Position.X, + Y: (rand.Float64() * 400) - 200 + p.me.Position.Y, + }.ToPoint() + return &pt +} diff --git a/player.go b/player.go index e54ed55..5bd2a94 100644 --- a/player.go +++ b/player.go @@ -1,12 +1,6 @@ package client -import ( - "math" - "math/rand" - - "hackerbots.us/server" - "hackerbots.us/vector" -) +import "hackerbots.us/server" // Player is the interface that is implemented when specifying non-default // player behavior. @@ -14,143 +8,22 @@ import ( // 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 { + + // GetStats returns a map with an entry for each robot the player will control + // containing the desired stats for that robot + GetStats() map[string]server.StatsRequest + + // Update is called on reciept of a board state packet and the response is + // the instructions for each robot in a map of robot id to instructions 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 -} - type Spectator struct{} +func (s Spectator) GetStats() map[string]server.StatsRequest { + return nil +} + func (s Spectator) Update(bs *server.Boardstate) map[string]server.Instruction { return nil } diff --git a/simple_player.go b/simple_player.go new file mode 100644 index 0000000..1957483 --- /dev/null +++ b/simple_player.go @@ -0,0 +1,151 @@ +package client + +import ( + "math" + "math/rand" + + "hackerbots.us/server" + "hackerbots.us/vector" +) + +// 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 + stats server.StatsRequest +} + +// NewSimplePlayer simply returns a populated, usable *SimplePlayer +func NewSimplePlayer(width, height float64, stats server.StatsRequest) *SimplePlayer { + return &SimplePlayer{ + knownObstacles: make(map[string]server.Obstacle), + width: width, + height: height, + maxSpeed: 100, + safeDistance: 40, + stats: stats, + } +} + +// GetStats returns a map with an entry for each robot the player will control +// containing the desired stats for that robot +func (p *SimplePlayer) GetStats() map[string]server.StatsRequest { + s := make(map[string]server.StatsRequest) + s["simple"] = p.stats + return s +} + +// 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 +}