Compare commits
25
Commits
robot-upgrade
..
viz
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15f1ad15f4 | ||
|
|
86a00042c3 | ||
|
|
f1ae71ecc0 | ||
|
|
f9984c1826 | ||
|
|
174be3b0ab | ||
|
|
9be5f226f5 | ||
|
|
6c67d83f49 | ||
|
|
c21e0c5582 | ||
|
|
35cb8431f6 | ||
|
|
70adb5b713 | ||
|
|
3668c09235 | ||
|
|
50be2c5b88 | ||
|
|
8a85c94767 | ||
|
|
623dfa4c48 | ||
|
|
3ef90af341 | ||
|
|
855208b39d | ||
|
|
84f151864b | ||
|
|
f6439cf41d | ||
|
|
f06ccd2588 | ||
|
|
bb072255cd | ||
|
|
14297191ed | ||
|
|
d3e7a247c8 | ||
|
|
2b710ef40d | ||
|
|
b074dda677 | ||
|
|
7d77f18470 |
@@ -5,36 +5,37 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"bitbucket.org/hackerbots/server"
|
"golang.org/x/net/websocket"
|
||||||
"code.google.com/p/go.net/websocket"
|
|
||||||
|
"hackerbots.us/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
func connect(server string, port int) (*websocket.Conn, error) {
|
func connect(addr string) (*websocket.Conn, error) {
|
||||||
origin := "http://localhost/"
|
origin := "http://localhost/"
|
||||||
url := fmt.Sprintf("ws://%s:%d/ws/", server, port)
|
url := fmt.Sprintf("%s/ws/", addr)
|
||||||
return websocket.Dial(url, "", origin)
|
return websocket.Dial(url, "", origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client keeps track of connection to server and has two interesting methods:
|
// Client keeps track of connection to server and has two interesting methods:
|
||||||
// Negociate and Play. Users of this struct will likely use most everything as
|
// Negotiate and Play. Users of this struct will likely use most everything as
|
||||||
// is while defining their own Player to specify desired game play behavior.
|
// is while defining their own Player to specify desired game play behavior.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ForceJSON bool
|
ForceJSON bool
|
||||||
GameId string
|
GameId string
|
||||||
Name string
|
Name string
|
||||||
Port int
|
|
||||||
Server string
|
Server string
|
||||||
StatsReq server.StatsRequest
|
|
||||||
Verbose bool
|
|
||||||
Player Player
|
|
||||||
Game server.GameParam
|
Game server.GameParam
|
||||||
|
|
||||||
boardstate server.Boardstate
|
// max dimensions of field
|
||||||
|
Width, Height float64
|
||||||
|
|
||||||
|
boardstate *server.Boardstate
|
||||||
enc encoder
|
enc encoder
|
||||||
dec decoder
|
dec decoder
|
||||||
ws *websocket.Conn
|
ws *websocket.Conn
|
||||||
|
|
||||||
|
Player
|
||||||
}
|
}
|
||||||
|
|
||||||
type encoder interface {
|
type encoder interface {
|
||||||
@@ -45,12 +46,9 @@ type decoder interface {
|
|||||||
Decode(v interface{}) error
|
Decode(v interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Negociate runs through the hackerbots negociation protocol.
|
// Negotiate runs through the hackerbots negociation protocol.
|
||||||
func (c *Client) Negociate() (err error) {
|
func (c *Client) Negotiate(clientType string, player Player) (err error) {
|
||||||
if c.Verbose {
|
c.ws, err = connect(c.Server)
|
||||||
log.Printf("%s: trying to connect to game '%s'", c.Name, c.GameId)
|
|
||||||
}
|
|
||||||
c.ws, err = connect(c.Server, c.Port)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(fmt.Sprintf("connection failure: %s", err))
|
return errors.New(fmt.Sprintf("connection failure: %s", err))
|
||||||
}
|
}
|
||||||
@@ -72,9 +70,6 @@ func (c *Client) Negociate() (err error) {
|
|||||||
if err != nil || idreq.Type == "failure" {
|
if err != nil || idreq.Type == "failure" {
|
||||||
return errors.New(fmt.Sprintf("failure: %+v", idreq))
|
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 {
|
err = websocket.JSON.Send(c.ws, struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@@ -83,7 +78,7 @@ func (c *Client) Negociate() (err error) {
|
|||||||
}{
|
}{
|
||||||
Name: c.Name,
|
Name: c.Name,
|
||||||
Useragent: "gobot",
|
Useragent: "gobot",
|
||||||
Type: "robot",
|
Type: clientType,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -102,9 +97,6 @@ func (c *Client) Negociate() (err error) {
|
|||||||
if c.Game.Type != "gameparam" {
|
if c.Game.Type != "gameparam" {
|
||||||
return errors.New("didn't receive a good 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" {
|
if c.Game.Encoding == "json" {
|
||||||
c.enc = json.NewEncoder(c.ws)
|
c.enc = json.NewEncoder(c.ws)
|
||||||
@@ -114,11 +106,11 @@ func (c *Client) Negociate() (err error) {
|
|||||||
c.dec = gob.NewDecoder(c.ws)
|
c.dec = gob.NewDecoder(c.ws)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch clientType {
|
||||||
|
case "robot":
|
||||||
conf := server.ClientConfig{
|
conf := server.ClientConfig{
|
||||||
ID: c.GameId,
|
ID: c.GameId,
|
||||||
Stats: map[string]server.StatsRequest{
|
Stats: player.GetStats(),
|
||||||
c.Name: c.StatsReq,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = websocket.JSON.Send(c.ws, conf)
|
err = websocket.JSON.Send(c.ws, conf)
|
||||||
@@ -133,9 +125,6 @@ func (c *Client) Negociate() (err error) {
|
|||||||
if !handshake.Success {
|
if !handshake.Success {
|
||||||
return errors.New(handshake.Reason)
|
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
|
// we don't do anything useful with dstats, but could be interesting to
|
||||||
// pass along to the player?
|
// pass along to the player?
|
||||||
@@ -143,10 +132,22 @@ func (c *Client) Negociate() (err error) {
|
|||||||
Stats map[string]server.Stats `json:"stats"`
|
Stats map[string]server.Stats `json:"stats"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
err = websocket.JSON.Receive(c.ws, &dstats)
|
err = websocket.JSON.Receive(c.ws, &dstats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ids := make(map[string]string)
|
||||||
|
for name, entry := range dstats.Stats {
|
||||||
|
ids[entry.Id] = name
|
||||||
|
}
|
||||||
|
player.SetIDs(ids)
|
||||||
|
|
||||||
|
case "spectator":
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Width = c.Game.BoardSize.Width
|
||||||
|
c.Height = c.Game.BoardSize.Height
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -155,22 +156,15 @@ func (c *Client) Negociate() (err error) {
|
|||||||
// server, and passes this along to the embedded Player. Following this it
|
// server, and passes this along to the embedded Player. Following this it
|
||||||
// sends the server the Player's instruction.
|
// sends the server the Player's instruction.
|
||||||
func (c *Client) Play() error {
|
func (c *Client) Play() error {
|
||||||
log.Printf("%s: starting loop", c.Name)
|
bs := &server.Boardstate{}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
for {
|
for {
|
||||||
err = c.dec.Decode(&c.boardstate)
|
err = c.dec.Decode(bs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(fmt.Sprintf("%s: Connection likely lost: %s", c.Name, err))
|
return errors.New(fmt.Sprintf("%s: Connection likely lost: %s", c.Name, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
instructions := make(map[string]server.Instruction)
|
err = c.enc.Encode(c.Update(bs))
|
||||||
|
|
||||||
for _,bot := range(c.boardstate.MyRobots){
|
|
||||||
instructions[bot.Id] = c.Player.Update(&bot, &c.boardstate)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.enc.Encode(instructions)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hackerbots.us/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
ui := client.NewSpectator(c.Width, c.Height)
|
||||||
|
c.Player = ui
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := c.Play(); err != nil {
|
||||||
|
close(ui.Die)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := ui.Spectate(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "problem during visualization: %+v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
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 [fraserbot, simple]")
|
||||||
|
var spectate = flag.Bool("spectate", false, "enable terminal visualizer")
|
||||||
|
|
||||||
|
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,
|
||||||
|
ForceJSON: *forceJSON,
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Negotiate("robot", c.Player); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: failed to negociate: %s\n", c.Name, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *spectate {
|
||||||
|
ui := client.NewSpectator(c.Width, c.Height)
|
||||||
|
players := client.MultiPlayer{
|
||||||
|
c.Player,
|
||||||
|
ui,
|
||||||
|
}
|
||||||
|
c.Player = players
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := c.Play(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "problem during play: %v\n", err)
|
||||||
|
close(ui.Die)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := ui.Spectate(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "problem during visualization: %+v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := c.Play(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "problem during play: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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.
|
||||||
@@ -1,187 +1,59 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import "hackerbots.us/server"
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"math/rand"
|
|
||||||
|
|
||||||
"bitbucket.org/hackerbots/server"
|
// Player is the interface that defines a player's behavior.
|
||||||
"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
|
// 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.
|
// required to slay other robots quickly while staying alive for a long time.
|
||||||
type Player interface {
|
type Player interface {
|
||||||
Update(bot *server.Robot, bs *server.Boardstate) server.Instruction
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// SimplePlayer is our default player and stands as a starting point for your
|
// MultiPlayer wraps multiple players and calls funcitons in Player in
|
||||||
// own Player implementations.
|
// appropriate order.
|
||||||
type SimplePlayer struct {
|
//
|
||||||
me server.Robot
|
// Typically used to add a Spectator to a bot.
|
||||||
width, height float64
|
type MultiPlayer []Player
|
||||||
knownObstacles map[string]server.Obstacle
|
|
||||||
nearestEnemy *server.OtherRobot
|
// GetStats implements GetStats for a collection of players asking each player
|
||||||
fireat *vector.Point2d
|
// for their stats, and returning the configuration of the first player.
|
||||||
moveto *vector.Point2d
|
func (mp MultiPlayer) GetStats() map[string]server.StatsRequest {
|
||||||
speed float64
|
var s map[string]server.StatsRequest
|
||||||
maxSpeed float64
|
for i := len(mp) - 1; i >= 0; i-- {
|
||||||
safeDistance float64
|
s = mp[i].GetStats()
|
||||||
|
}
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSimplePlayer simply returns a populated, usable *SimplePlayer
|
// SetIDs passes ids to all players.
|
||||||
func NewSimplePlayer(width, height float64) *SimplePlayer {
|
func (mp MultiPlayer) SetIDs(ids map[string]string) {
|
||||||
return &SimplePlayer{
|
for _, p := range mp {
|
||||||
knownObstacles: make(map[string]server.Obstacle),
|
p.SetIDs(ids)
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
maxSpeed: 1000,
|
|
||||||
safeDistance: 50,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recv is our implementation of receiving a server.Boardstate from the server
|
// Update implements Update for a collection of players sending board state to
|
||||||
func (p *SimplePlayer) Update(bot *server.Robot, bs *server.Boardstate) server.Instruction{
|
// each player, and returning the instructions associated with the first
|
||||||
p.me = *bot
|
// player.
|
||||||
p.speed = 1000
|
func (mp MultiPlayer) Update(bs *server.Boardstate) map[string]server.Instruction {
|
||||||
if p.me.Health <= 0{
|
var inst map[string]server.Instruction
|
||||||
return server.Instruction{}
|
for i := len(mp) - 1; i >= 0; i-- {
|
||||||
}
|
inst = mp[i].Update(bs)
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
return inst
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
+227
@@ -0,0 +1,227 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"hackerbots.us/server"
|
||||||
|
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
const d = 'v'
|
||||||
|
const u = '^'
|
||||||
|
const r = '>'
|
||||||
|
const l = '<'
|
||||||
|
|
||||||
|
// NewSpectator initializes and returns a *Spectator.
|
||||||
|
func NewSpectator(w, h float64) *Spectator {
|
||||||
|
return &Spectator{
|
||||||
|
StateStream: make(chan *server.Boardstate),
|
||||||
|
Die: make(chan struct{}),
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spectator encodes a termbox ui.
|
||||||
|
type Spectator struct {
|
||||||
|
// max dimensions of field
|
||||||
|
width, height float64
|
||||||
|
|
||||||
|
// dimensions of the terminal window
|
||||||
|
viewX, viewY int
|
||||||
|
|
||||||
|
StateStream chan *server.Boardstate
|
||||||
|
|
||||||
|
// when closed will cause the Spectator to exit the render loop.
|
||||||
|
Die chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIDs is implemented so Spectator can be used as a client.Player.
|
||||||
|
func (s Spectator) SetIDs(map[string]string) {}
|
||||||
|
|
||||||
|
// GetStats is implemented so Spectator can be used as a client.Player.
|
||||||
|
func (s Spectator) GetStats() map[string]server.StatsRequest { return nil }
|
||||||
|
|
||||||
|
// Update is implemented so Spectator can be used as a client.Player.
|
||||||
|
func (s Spectator) Update(bs *server.Boardstate) map[string]server.Instruction {
|
||||||
|
s.StateStream <- bs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spectate runs the termbox render loop.
|
||||||
|
func (s *Spectator) Spectate() error {
|
||||||
|
err := termbox.Init()
|
||||||
|
if err != nil {
|
||||||
|
return 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 'f':
|
||||||
|
termbox.SetCell(
|
||||||
|
20,
|
||||||
|
20,
|
||||||
|
'*',
|
||||||
|
termbox.ColorRed, termbox.ColorBlack,
|
||||||
|
)
|
||||||
|
case 'c':
|
||||||
|
termbox.Clear(termbox.ColorBlack, termbox.ColorBlack)
|
||||||
|
}
|
||||||
|
|
||||||
|
case termbox.EventResize:
|
||||||
|
s.viewX, s.viewY = event.Width, event.Height
|
||||||
|
case termbox.EventError:
|
||||||
|
err = fmt.Errorf("Quitting because of termbox error:\n%v\n", event.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case update := <-s.StateStream:
|
||||||
|
termbox.Clear(termbox.ColorBlack, termbox.ColorBlack)
|
||||||
|
|
||||||
|
for _, obstacle := range update.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 update.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 = r
|
||||||
|
} else {
|
||||||
|
b = l
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if bot.Heading.Y > 0 {
|
||||||
|
b = u
|
||||||
|
} else {
|
||||||
|
b = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c := termbox.ColorRed
|
||||||
|
if bot.Health <= 0 {
|
||||||
|
c = termbox.ColorBlack
|
||||||
|
}
|
||||||
|
termbox.SetCell(
|
||||||
|
x,
|
||||||
|
s.viewY-y,
|
||||||
|
b,
|
||||||
|
c, termbox.ColorBlack,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range update.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 update.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 update.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 = r
|
||||||
|
} else {
|
||||||
|
b = l
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if bot.Heading.Y > 0 {
|
||||||
|
b = u
|
||||||
|
} else {
|
||||||
|
b = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
case <-s.Die:
|
||||||
|
err = errors.New("was told to die")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
termbox.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user