Compare commits

..

2 Commits

Author SHA1 Message Date
Fraser Graham
f8ecde3e81 Improving basic swarm bot 2014-04-27 00:49:16 -06:00
Fraser Graham
a2436fe8d8 chaneg the player interface to be more consistent with the JS version 2014-04-26 13:59:32 -06:00
9 changed files with 314 additions and 729 deletions

128
client.go
View File

@ -5,40 +5,36 @@ import (
"encoding/json"
"errors"
"fmt"
"log"
"golang.org/x/net/websocket"
"hackerbots.us/server"
"bitbucket.org/hackerbots/server"
"code.google.com/p/go.net/websocket"
)
func connect(addr string) (*websocket.Conn, error) {
func connect(server string, port int) (*websocket.Conn, error) {
origin := "http://localhost/"
url := fmt.Sprintf("%s/ws/", addr)
url := fmt.Sprintf("ws://%s:%d/ws/", server, port)
return websocket.Dial(url, "", origin)
}
// Client keeps track of connection to server and has two interesting methods:
// Negotiate and Play. Users of this struct will likely use most everything as
// Negociate and Play. Users of this struct will likely use most everything as
// is while defining their own Player to specify desired game play behavior.
type Client struct {
ForceJSON bool
GameId string
Name string
Port int
Server string
StatsReq server.StatsRequest
Verbose bool
Player Player
Game server.GameParam
boardstate *server.Boardstate
boardstate server.Boardstate
enc encoder
dec decoder
ws *websocket.Conn
// visualization members
width, height float64
viewX, viewY int
StateStream chan *server.Boardstate
Die chan struct{}
Player
}
type encoder interface {
@ -49,9 +45,12 @@ type decoder interface {
Decode(v interface{}) error
}
// Negotiate runs through the hackerbots negociation protocol.
func (c *Client) Negotiate(clientType string, player Player) (err error) {
c.ws, err = connect(c.Server)
// Negociate runs through the hackerbots negociation protocol.
func (c *Client) Negociate() (err error) {
if c.Verbose {
log.Printf("%s: trying to connect to game '%s'", c.Name, c.GameId)
}
c.ws, err = connect(c.Server, c.Port)
if err != nil {
return errors.New(fmt.Sprintf("connection failure: %s", err))
}
@ -73,6 +72,9 @@ func (c *Client) Negotiate(clientType string, player Player) (err error) {
if err != nil || idreq.Type == "failure" {
return errors.New(fmt.Sprintf("failure: %+v", idreq))
}
if c.Verbose {
log.Printf("%s: idreq: %+v", c.Name, idreq)
}
err = websocket.JSON.Send(c.ws, struct {
Type string `json:"type"`
@ -81,7 +83,7 @@ func (c *Client) Negotiate(clientType string, player Player) (err error) {
}{
Name: c.Name,
Useragent: "gobot",
Type: clientType,
Type: "robot",
})
if err != nil {
return err
@ -100,6 +102,9 @@ func (c *Client) Negotiate(clientType string, player Player) (err error) {
if c.Game.Type != "gameparam" {
return errors.New("didn't receive a good gameparam")
}
if c.Verbose {
log.Printf("%s: game parameters: %+v", c.Name, c.Game)
}
if c.Game.Encoding == "json" {
c.enc = json.NewEncoder(c.ws)
@ -109,48 +114,39 @@ func (c *Client) Negotiate(clientType string, player Player) (err error) {
c.dec = gob.NewDecoder(c.ws)
}
switch clientType {
case "robot":
conf := server.ClientConfig{
ID: c.GameId,
Stats: player.GetStats(),
}
err = websocket.JSON.Send(c.ws, conf)
var handshake struct {
Id string `json:"id"`
Success bool `json:"success"`
Type string `json:"type"`
server.Failure
}
websocket.JSON.Receive(c.ws, &handshake)
if !handshake.Success {
return errors.New(handshake.Reason)
}
// we don't do anything useful with dstats, but could be interesting to
// pass along to the player?
dstats := struct {
Stats map[string]server.Stats `json:"stats"`
Type string `json:"type"`
}{}
err = websocket.JSON.Receive(c.ws, &dstats)
if err != nil {
return err
}
ids := make(map[string]string)
for name, entry := range dstats.Stats {
ids[entry.Id] = name
}
player.SetIDs(ids)
case "spectator":
conf := server.ClientConfig{
ID: c.GameId,
Stats: map[string]server.StatsRequest{
c.Name: c.StatsReq,
},
}
c.width = c.Game.BoardSize.Width
c.height = c.Game.BoardSize.Height
err = websocket.JSON.Send(c.ws, conf)
var handshake struct {
Id string `json:"id"`
Success bool `json:"success"`
Type string `json:"type"`
server.Failure
}
websocket.JSON.Receive(c.ws, &handshake)
if !handshake.Success {
return errors.New(handshake.Reason)
}
if c.Verbose {
log.Printf("%s: handshake: %+v", c.Name, handshake)
}
// we don't do anything useful with dstats, but could be interesting to
// pass along to the player?
dstats := struct {
Stats map[string]server.Stats `json:"stats"`
Type string `json:"type"`
}{}
err = websocket.JSON.Receive(c.ws, &dstats)
if err != nil {
return err
}
return nil
}
@ -159,20 +155,22 @@ func (c *Client) Negotiate(clientType string, player Player) (err error) {
// server, and passes this along to the embedded Player. Following this it
// sends the server the Player's instruction.
func (c *Client) Play() error {
bs := &server.Boardstate{}
log.Printf("%s: starting loop", c.Name)
var err error
for {
err = c.dec.Decode(bs)
err = c.dec.Decode(&c.boardstate)
if err != nil {
return errors.New(fmt.Sprintf("%s: Connection likely lost: %s", c.Name, err))
}
select {
case c.StateStream <- bs:
default:
instructions := make(map[string]server.Instruction)
for _,bot := range(c.boardstate.MyRobots){
instructions[bot.Id] = c.Player.Update(&bot, &c.boardstate)
}
err = c.enc.Encode(c.Update(bs))
err = c.enc.Encode(instructions)
if err != nil {
return err
}

View File

@ -1,49 +0,0 @@
package main
import (
"flag"
"fmt"
"math/rand"
"os"
"time"
"hackerbots.us/client"
"hackerbots.us/server"
)
var addr = flag.String("addr", "ws://localhost:8666", "server hostname")
var forceJSON = flag.Bool("json", false, "force json encoding")
func main() {
rand.Seed(time.Now().UnixNano())
var gameId string
flag.Parse()
if flag.NArg() < 1 {
gameId = "debug"
} else {
gameId = flag.Arg(0)
}
c := &client.Client{
Server: *addr,
Name: "bspect",
GameId: gameId,
ForceJSON: *forceJSON,
StateStream: make(chan *server.Boardstate),
Die: make(chan struct{}),
Player: client.Spectator{},
}
var err error
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)
}
go func() {
if err := c.Play(); err != nil {
close(c.Die)
}
}()
c.Visualize()
}

View File

@ -1,85 +0,0 @@
package main
import (
"flag"
"fmt"
"math/rand"
"os"
"time"
"hackerbots.us/client"
"hackerbots.us/server"
)
var hp = flag.Int("hp", 50, "")
var speed = flag.Int("speed", 50, "")
var acceleration = flag.Int("acceleration", 50, "")
var scannerRadius = flag.Int("srad", 50, "scanner radius")
var turnSpeed = flag.Int("omega", 50, "turn speed")
var fireRate = flag.Int("fire-rate", 50, "scanner radius")
var weaponRadius = flag.Int("wrad", 50, "weapon radius")
var weaponDamage = flag.Int("wdamage", 50, "weapons umph")
var weaponSpeed = flag.Int("wspeed", 50, "weapons speed")
// XXX: add TurnSpeed, WeaponDamage, WeaponSpeed
var addr = flag.String("addr", "ws://localhost:8666", "server hostname")
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())
var gameId string
flag.Parse()
if flag.NArg() < 1 {
gameId = "debug"
} else {
gameId = flag.Arg(0)
}
c := &client.Client{
Server: *addr,
Name: *botname,
GameId: gameId,
// XXX: update with missing fields
ForceJSON: *forceJSON,
StateStream: make(chan *server.Boardstate),
Die: make(chan struct{}),
}
sr := server.StatsRequest{
Hp: *hp,
Speed: *speed,
Acceleration: *acceleration,
ScannerRadius: *scannerRadius,
TurnSpeed: *turnSpeed,
FireRate: *fireRate,
WeaponRadius: *weaponRadius,
WeaponDamage: *weaponDamage,
WeaponSpeed: *weaponSpeed,
}
switch *botType {
case "simple":
c.Player = client.NewSimplePlayer(800, 600, sr)
case "fraserbot":
c.Player = client.NewFraserbot("Fraserbot")
default:
fmt.Fprintf(os.Stderr, "must specify a known bot \n")
os.Exit(1)
}
var err error
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)
}
go func() {
if err := c.Play(); err != nil {
close(c.Die)
}
}()
c.Visualize()
}

View File

@ -1,134 +0,0 @@
package client
import (
"fmt"
"log"
"math/rand"
"hackerbots.us/server"
"hackerbots.us/vector"
)
// Fraserbot is a bad ass motherfucker, that will fuck SHIT UUUUUP
type Fraserbot struct {
knownObstacles map[string]server.Obstacle
nearestEnemy *server.OtherRobot
fireat *vector.Point2d
moveto *vector.Point2d
name string
botIDs map[string]string
}
// NewFraserbot simply returns a populated, usable *Fraserbot
func NewFraserbot(name string) *Fraserbot {
return &Fraserbot{
knownObstacles: make(map[string]server.Obstacle),
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: 100,
Speed: 10,
Acceleration: 10,
ScannerRadius: 10,
TurnSpeed: 10,
FireRate: 30,
WeaponRadius: 20,
WeaponDamage: 30,
WeaponSpeed: 30,
}
s[fmt.Sprintf("%v_Jr", p.name)] = server.StatsRequest{
Hp: 10,
Speed: 100,
Acceleration: 10,
ScannerRadius: 60,
TurnSpeed: 48,
FireRate: 1,
WeaponRadius: 10,
WeaponDamage: 10,
WeaponSpeed: 1,
}
return s
}
// SetIDs provides the mapping of names to ID's for each bot
func (p *Fraserbot) SetIDs(ids map[string]string) {
p.botIDs = ids
log.Println(ids)
}
// 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 {
me := bot
// We're just starting out
if p.moveto == nil {
p.moveto = &me.Position
}
speed := float64(200)
// If we're close to where we want to go then pick a new
// place to go, we done good
if me.Position.Sub(*p.moveto).Mag() < 30 {
p.moveto = p.randomDirection(me.Position, 400)
}
if me.ProbeResult != nil && me.ProbeResult.Type == "obstacle" {
speed = -50
p.moveto = p.randomDirection(me.Position, 600)
}
if len(me.Scanners) > 0 {
// Find the bots that are not mine
var index int
found := false
for i, entry := range me.Scanners {
_, ok := p.botIDs[entry.Id]
if !ok {
index = i
found = true
break
}
}
if found {
// log.Println(me.Scanners[0])
for _, bot := range bs.OtherRobots {
if bot.Id == me.Scanners[index].Id {
p.fireat = &bot.Position
log.Printf("%v Found Enemy: %v\n", me.Name, bot.Name)
}
}
}
}
instructions[bot.Id] = server.Instruction{
MoveTo: p.moveto,
TargetSpeed: &speed,
FireAt: p.fireat,
Probe: p.moveto,
}
}
return instructions
}
// randomDirection is a spot within 200 of the current position
func (p *Fraserbot) randomDirection(pos vector.Point2d, dist float64) *vector.Point2d {
pt := vector.Vector2d{
X: (rand.Float64() * dist) - (dist / 2) + pos.X,
Y: (rand.Float64() * dist) - (dist / 2) + pos.Y,
}.ToPoint()
return &pt
}

78
gobot/main.go Normal file
View File

@ -0,0 +1,78 @@
package main
import (
"flag"
"log"
"math/rand"
"time"
"bitbucket.org/hackerbots/client"
"bitbucket.org/hackerbots/server"
)
var hp = flag.Int("hp", 50, "")
var speed = flag.Int("speed", 50, "")
var acceleration = flag.Int("acceleration", 50, "")
var scannerRadius = flag.Int("srad", 50, "scanner radius")
var turnSpeed = flag.Int("omega", 50, "turn speed")
var fireRate = flag.Int("fire-rate", 50, "scanner radius")
var weaponRadius = flag.Int("wrad", 50, "weapon radius")
var weaponDamage = flag.Int("wdamage", 50, "weapons umph")
var weaponSpeed = flag.Int("wspeed", 50, "weapons speed")
// XXX: add TurnSpeed, WeaponDamage, WeaponSpeed
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 verbose = flag.Bool("verbose", false, "run verbosly")
var forceJSON = flag.Bool("json", false, "force json encoding")
func main() {
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
rand.Seed(time.Now().UnixNano())
var gameId string
flag.Parse()
if flag.NArg() < 1 {
gameId = "debug"
} else {
gameId = flag.Arg(0)
}
client.Verbose = *verbose
c := &client.Client{
Server: *serverHostname,
Port: *port,
Name: *botname,
GameId: gameId,
// XXX: update with missing fields
StatsReq: server.StatsRequest{
Hp: *hp,
Speed: *speed,
Acceleration: *acceleration,
ScannerRadius: *scannerRadius,
TurnSpeed: *turnSpeed,
FireRate: *fireRate,
WeaponRadius: *weaponRadius,
WeaponDamage: *weaponDamage,
WeaponSpeed: *weaponSpeed,
},
Verbose: *verbose,
ForceJSON: *forceJSON,
}
var err error
err = c.Negociate()
if err != nil {
log.Fatalf("%s: failed to negociate: %s", c.Name, err)
}
c.Player = client.NewSimplePlayer(
c.Game.BoardSize.Width,
c.Game.BoardSize.Height,
)
if err := c.Play(); err != nil {
log.Fatal(err)
}
}

27
license
View File

@ -1,27 +0,0 @@
Copyright (c) 2016, Fraser Graham, Stephen McQuay
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of server nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

197
player.go
View File

@ -1,6 +1,15 @@
package client
import "hackerbots.us/server"
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.
@ -8,31 +17,171 @@ import "hackerbots.us/server"
// 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
// SetIDs is called from the client once the server
// has accepted the robots supplied in GetStats and validated
// their config, the data passed into SetIDs is a mapping of
// bot name to server side bot ID that is used in all bot
// dats sent from the server
SetIDs(map[string]string)
// 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
Update(bot *server.Robot, bs *server.Boardstate) server.Instruction
}
type Spectator struct{}
func (s Spectator) SetIDs(map[string]string) {}
func (s Spectator) GetStats() map[string]server.StatsRequest {
return nil
// 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
}
func (s Spectator) Update(bs *server.Boardstate) map[string]server.Instruction {
return nil
// 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
}

View File

@ -1,153 +0,0 @@
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,
}
}
func (p *SimplePlayer) SetIDs(map[string]string) {}
// 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
}

View File

@ -1,192 +0,0 @@
package client
import (
"fmt"
"math"
"github.com/nsf/termbox-go"
"hackerbots.us/server"
)
var botDown rune = 'v'
var botUp rune = '^'
var botRight rune = '>'
var botLeft rune = '<'
type size struct {
width, height int
}
// Recv is our implementation of receiving a server.Boardstate from the server
func (s *Client) Recv(bs *server.Boardstate) map[string]server.Instruction {
s.StateStream <- bs
return nil
}
func (s *Client) Visualize() {
err := termbox.Init()
if err != nil {
panic(err)
}
s.viewX, s.viewY = termbox.Size()
events := make(chan termbox.Event, 1024)
go func() {
for {
events <- termbox.PollEvent()
}
}()
termbox.HideCursor()
termbox.Clear(termbox.ColorBlack, termbox.ColorBlack)
func() {
for {
select {
case event := <-events:
switch event.Type {
case termbox.EventKey:
switch event.Key {
case termbox.KeyCtrlZ, termbox.KeyCtrlC:
return
}
switch event.Ch {
case 'q':
return
case 'c':
termbox.Clear(termbox.ColorBlack, termbox.ColorBlack)
}
case termbox.EventResize:
s.viewX, s.viewY = event.Width, event.Height
case termbox.EventError:
panic(fmt.Sprintf("Quitting because of termbox error:\n%v\n", event.Err))
}
case u := <-s.StateStream:
termbox.Clear(termbox.ColorBlack, termbox.ColorBlack)
for _, obstacle := range u.Obstacles {
startX := int((obstacle.Bounds.A.X / s.width) * float64(s.viewX))
stopX := int((obstacle.Bounds.B.X / s.width) * float64(s.viewX))
startY := int((obstacle.Bounds.A.Y / s.height) * float64(s.viewY))
stopY := int((obstacle.Bounds.B.Y / s.height) * float64(s.viewY))
for x := startX; x < stopX; x++ {
for y := startY; y < stopY; y++ {
termbox.SetCell(
x,
s.viewY-y,
' ',
termbox.ColorBlack, termbox.ColorBlue,
)
}
}
}
for _, bot := range u.OtherRobots {
x := int((bot.Position.X / s.width) * float64(s.viewX))
y := int((bot.Position.Y / s.height) * float64(s.viewY))
var b rune
if math.Abs(bot.Heading.X) > math.Abs(bot.Heading.Y) {
if bot.Heading.X > 0 {
b = botRight
} else {
b = botLeft
}
} else {
if bot.Heading.Y > 0 {
b = botUp
} else {
b = botDown
}
}
c := termbox.ColorRed
if bot.Health <= 0 {
c = termbox.ColorBlack
}
termbox.SetCell(
x,
s.viewY-y,
b,
c, termbox.ColorBlack,
)
}
for _, p := range u.Projectiles {
x := int((p.Position.X / s.width) * float64(s.viewX))
y := int((p.Position.Y / s.height) * float64(s.viewY))
termbox.SetCell(
x,
s.viewY-y,
'·',
termbox.ColorWhite|termbox.AttrBold, termbox.ColorBlack,
)
}
for _, splosion := range u.Splosions {
startX := int(((splosion.Position.X - float64(splosion.Radius)) / s.width) * float64(s.viewX))
startY := int(((splosion.Position.Y - float64(splosion.Radius)) / s.height) * float64(s.viewY))
stopX := int(((splosion.Position.X+float64(splosion.Radius))/s.width)*float64(s.viewX)) + 1
stopY := int(((splosion.Position.Y+float64(splosion.Radius))/s.height)*float64(s.viewY)) + 1
for x := startX; x < stopX; x++ {
for y := startY; y < stopY; y++ {
realX := float64(x) * s.width / float64(s.viewX)
realY := float64(y) * s.height / float64(s.viewY)
dX := realX - splosion.Position.X
dY := realY - splosion.Position.Y
curRad := math.Sqrt(dX*dX + dY*dY)
if curRad < float64(splosion.Radius) {
termbox.SetCell(
x,
s.viewY-y,
'·',
termbox.ColorYellow|termbox.AttrBold, termbox.ColorRed,
)
}
}
}
}
for _, bot := range u.MyRobots {
x := int((bot.Position.X / s.width) * float64(s.viewX))
y := int((bot.Position.Y / s.height) * float64(s.viewY))
var b rune
if math.Abs(bot.Heading.X) > math.Abs(bot.Heading.Y) {
if bot.Heading.X > 0 {
b = botRight
} else {
b = botLeft
}
} else {
if bot.Heading.Y > 0 {
b = botUp
} else {
b = botDown
}
}
c := termbox.ColorWhite
if bot.Health <= 0 {
c = termbox.ColorBlack
}
termbox.SetCell(
x,
s.viewY-y,
b,
c|termbox.AttrBold, termbox.ColorBlack,
)
}
err := termbox.Flush()
if err != nil {
panic(err)
}
case <-s.Die:
return
}
}
}()
termbox.Close()
}