First pass at documentations.
This commit is contained in:
parent
0cf0957d73
commit
faacfe7fd9
11
config.go
11
config.go
@ -11,6 +11,9 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config embodies the configuration for a game. These are populated in various
|
||||
// ways (json POSTed to server, config file, constants) and are typically owned
|
||||
// by a Controller.
|
||||
type Config struct {
|
||||
Tick int `json:"tick"` // ms
|
||||
Timescale float32 `json:"timescale"`
|
||||
@ -23,8 +26,8 @@ type Config struct {
|
||||
}
|
||||
|
||||
const (
|
||||
TICK = 60
|
||||
TIMESCALE = 1.0
|
||||
TICK = 60 // ms, this is how often physics is updated
|
||||
TIMESCALE = 1.0 // this tweaks the temporal duration of a TICK
|
||||
WIDTH = 800
|
||||
HEIGHT = 550
|
||||
OBSTACLES = 5
|
||||
@ -32,6 +35,10 @@ const (
|
||||
DEFAULT_MODE = "deathmatch"
|
||||
)
|
||||
|
||||
// LoadConfig takes the location of a json file that contains values desired to
|
||||
// be used in the creation of games. Priority is given to values specified at
|
||||
// game cration time using control channel, followed by values in config file,
|
||||
// followed by constants defined above.
|
||||
func LoadConfig(filename string) (Config, error) {
|
||||
c := Config{
|
||||
Tick: TICK,
|
||||
|
31
control.go
31
control.go
@ -13,13 +13,18 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// JsonHandler is a function type that allows setting the Content-Type
|
||||
// appropriately for views destined to serve JSON
|
||||
type JsonHandler func(http.ResponseWriter, *http.Request)
|
||||
|
||||
// ServeHTTP is JsonHandler's http.Handler implementation.
|
||||
func (h JsonHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
h(w, req)
|
||||
}
|
||||
|
||||
// Controller is the shepherd of a collection of games. The main package in
|
||||
// botserv simply populates one of these and starts an http server.
|
||||
type Controller struct {
|
||||
Idg *IdGenerator
|
||||
Conf Config
|
||||
@ -28,6 +33,9 @@ type Controller struct {
|
||||
Profile string
|
||||
}
|
||||
|
||||
// NewController takes a populated Config, and some parameters to determine
|
||||
// what sorts of profiling to deal with and returns a freshly populated
|
||||
// Controller.
|
||||
func NewController(conf Config, mprof, pprof string) *Controller {
|
||||
idg := NewIdGenerator()
|
||||
return &Controller{
|
||||
@ -41,11 +49,15 @@ func NewController(conf Config, mprof, pprof string) *Controller {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Eventually this thing will have a select loop for dealing with game access in a more lock-free manner?
|
||||
// TODO Eventually this thing will have a select loop for dealing with game
|
||||
// access in a more lock-free manner?
|
||||
func (c *Controller) Run() {
|
||||
c.Idg.Run()
|
||||
}
|
||||
|
||||
// StartGame is the http route responsible for responding to requests to start
|
||||
// games under this controller. Creates a default Config object, and populates
|
||||
// it according to data POSTed.
|
||||
func (c *Controller) StartGame(w http.ResponseWriter, req *http.Request) {
|
||||
log.Println("asked to create a game")
|
||||
|
||||
@ -112,6 +124,8 @@ func (c *Controller) StartGame(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// ListGames makes a reasonable JSON response based on the games currently
|
||||
// being run.
|
||||
func (c *Controller) ListGames(w http.ResponseWriter, req *http.Request) {
|
||||
log.Println("games list requested")
|
||||
c.Games.RLock()
|
||||
@ -146,6 +160,8 @@ func (c *Controller) ListGames(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// GameStats provides an control mechanism to query for the stats of a single
|
||||
// game
|
||||
func (c *Controller) GameStats(w http.ResponseWriter, req *http.Request) {
|
||||
// TODO: wrap this up in something similar to the JsonHandler to verify the
|
||||
// url? Look at gorilla routing?
|
||||
@ -171,6 +187,8 @@ func (c *Controller) GameStats(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// BW provides a route to query for current bandwidth utilization for a single
|
||||
// game.
|
||||
func (c *Controller) BW(w http.ResponseWriter, req *http.Request) {
|
||||
// TODO: wrap this up in something similar to the JsonHandler to verify the
|
||||
// url? Look at gorilla routing?
|
||||
@ -198,7 +216,8 @@ func (c *Controller) BW(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// StopGame is the only mechanism to decrease the number of running games in a Controller
|
||||
// StopGame is the only mechanism to decrease the number of running games in
|
||||
// a Controller
|
||||
func (c *Controller) StopGame(w http.ResponseWriter, req *http.Request) {
|
||||
key, err := c.getGameId(req.URL.Path)
|
||||
if err != nil {
|
||||
@ -228,6 +247,8 @@ func (c *Controller) StopGame(w http.ResponseWriter, req *http.Request) {
|
||||
log.Printf("returning from StopGame")
|
||||
}
|
||||
|
||||
// KillServer is my favorite method of all the methods in botserv: it shuts
|
||||
// things down respecting profiling requests.
|
||||
func (c *Controller) KillServer(w http.ResponseWriter, req *http.Request) {
|
||||
if c.Profile != "" {
|
||||
log.Print("trying to stop cpu profile")
|
||||
@ -247,6 +268,9 @@ func (c *Controller) KillServer(w http.ResponseWriter, req *http.Request) {
|
||||
log.Fatal("shit got fucked up")
|
||||
}
|
||||
|
||||
// Index is the function for handling all traffic not officially in the API. It
|
||||
// just lets people know that this is a hackerbots server running at
|
||||
// a particular version.
|
||||
func (c *Controller) Index(w http.ResponseWriter, req *http.Request) {
|
||||
log.Println("version requested")
|
||||
version := struct {
|
||||
@ -261,6 +285,8 @@ func (c *Controller) Index(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// getGameId trims the gameid off of the url. This is hokey, and makes me miss
|
||||
// django regex-specified routes.
|
||||
func (c *Controller) getGameId(path string) (string, error) {
|
||||
var err error
|
||||
trimmed := strings.Trim(path, "/")
|
||||
@ -273,6 +299,7 @@ func (c *Controller) getGameId(path string) (string, error) {
|
||||
}
|
||||
|
||||
// MapLock is simply a map and a RWMutex
|
||||
// TODO: obviate the need for this in Controller.Run
|
||||
type MapLock struct {
|
||||
M map[string]*Game
|
||||
sync.RWMutex
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"log"
|
||||
)
|
||||
|
||||
// deathmatch is a game type that resets when there is one bot standing. There
|
||||
// is an obvious winner each round.
|
||||
type deathmatch struct {
|
||||
}
|
||||
|
||||
|
31
game.go
31
game.go
@ -13,16 +13,21 @@ import (
|
||||
|
||||
const maxPlayer = 128
|
||||
|
||||
// BotHealth is sent to all players so they know how other robots are
|
||||
// doing.
|
||||
type BotHealth struct {
|
||||
RobotId string `json:"robot_id"`
|
||||
Health int `json:"health"`
|
||||
}
|
||||
|
||||
// Scanner contains a Robot/Projectile hash and is sent to the user to
|
||||
// let them know which things they know about.
|
||||
type Scanner struct {
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// BotStats is stats for a single Player's Robot.
|
||||
type BotStats struct {
|
||||
Kills int
|
||||
Deaths int
|
||||
@ -33,25 +38,31 @@ type BotStats struct {
|
||||
Wins int
|
||||
}
|
||||
|
||||
// PlayerStats is what you want many of. Contains a map of BotStats and total
|
||||
// wins.
|
||||
type PlayerStats struct {
|
||||
BotStats map[string]*BotStats
|
||||
Wins int
|
||||
}
|
||||
|
||||
// GameStats is a collection of PlayerStats for all players involved.
|
||||
type GameStats struct {
|
||||
PlayerStats map[string]*PlayerStats
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// Game is the main point of interest in this application. Embodies all info
|
||||
// required to keep track of players, robots, stats, projectils, etc.
|
||||
// Currently Controllers have a map of these.
|
||||
type Game struct {
|
||||
id string
|
||||
players map[*player]bool
|
||||
players map[*Player]bool
|
||||
projectiles map[*Projectile]bool
|
||||
splosions map[*Splosion]bool
|
||||
obstacles []Obstacle
|
||||
obstacle_count int
|
||||
register chan *player
|
||||
unregister chan *player
|
||||
register chan *Player
|
||||
unregister chan *Player
|
||||
turn int
|
||||
players_remaining int
|
||||
width, height float32
|
||||
@ -68,12 +79,14 @@ type Game struct {
|
||||
bw *bandwidth.Bandwidth
|
||||
}
|
||||
|
||||
// This is the interface that different gametypes should implement.
|
||||
type GameMode interface {
|
||||
setup(g *Game)
|
||||
tick(gg *Game, payload *Boardstate)
|
||||
gameOver(gg *Game) (bool, *GameOver)
|
||||
}
|
||||
|
||||
// NewGame Poplulates a Game struct and starts the bandwidth calculator.
|
||||
func NewGame(id string, width, height float32, obstacles, tick, maxPoints int, mode string) (*Game, error) {
|
||||
bw, err := bandwidth.NewBandwidth(
|
||||
[]int{1, 10, 60},
|
||||
@ -85,13 +98,13 @@ func NewGame(id string, width, height float32, obstacles, tick, maxPoints int, m
|
||||
go bw.Run()
|
||||
g := &Game{
|
||||
id: id,
|
||||
register: make(chan *player, maxPlayer),
|
||||
unregister: make(chan *player, maxPlayer),
|
||||
register: make(chan *Player, maxPlayer),
|
||||
unregister: make(chan *Player, maxPlayer),
|
||||
projectiles: make(map[*Projectile]bool),
|
||||
splosions: make(map[*Splosion]bool),
|
||||
obstacles: GenerateObstacles(obstacles, width, height),
|
||||
obstacle_count: obstacles,
|
||||
players: make(map[*player]bool),
|
||||
players: make(map[*Player]bool),
|
||||
turn: 0,
|
||||
width: width,
|
||||
height: height,
|
||||
@ -119,6 +132,7 @@ func NewGame(id string, width, height float32, obstacles, tick, maxPoints int, m
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// tick is the method called every TICK ms.
|
||||
func (g *Game) tick(payload *Boardstate) {
|
||||
g.players_remaining = 0
|
||||
payload.Objects = MinifyObstacles(g.obstacles)
|
||||
@ -174,6 +188,8 @@ func (g *Game) tick(payload *Boardstate) {
|
||||
}
|
||||
}
|
||||
|
||||
// sendUpdate is what we use to determine what data goes out to each client;
|
||||
// performs filtering and sorting of the data.
|
||||
func (g *Game) sendUpdate(payload *Boardstate) {
|
||||
// Ensure that the robots are always sent in a consistent order
|
||||
sort.Sort(RobotSorter{Robots: payload.OtherRobots})
|
||||
@ -270,6 +286,7 @@ func (g *Game) sendUpdate(payload *Boardstate) {
|
||||
|
||||
}
|
||||
|
||||
// run is the method that contians the main game loop.
|
||||
func (g *Game) run() {
|
||||
ticker := time.NewTicker(time.Duration(g.tick_duration) * time.Millisecond)
|
||||
for {
|
||||
@ -323,6 +340,8 @@ func (g *Game) run() {
|
||||
log.Println("run done")
|
||||
}
|
||||
|
||||
// sendGameOver is a special method that sends a GameOver object to the clients
|
||||
// instead of a normal Boardstate message.
|
||||
func (g *Game) sendGameOver(eg *GameOver) {
|
||||
log.Printf("sending out game over message: %+v", eg)
|
||||
for p := range g.players {
|
||||
|
5
id.go
5
id.go
@ -23,6 +23,8 @@ func NewIdGenerator() *IdGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
// Run is called (typically in a gorotine) to allow for queries to be made
|
||||
// against IdGenerator.id throgh the Hash method.
|
||||
func (idg *IdGenerator) Run() {
|
||||
var i int64
|
||||
for i = 0; ; i++ {
|
||||
@ -30,6 +32,9 @@ func (idg *IdGenerator) Run() {
|
||||
}
|
||||
}
|
||||
|
||||
// Hash is the method used by a properly instantiated IdGenerator that gives
|
||||
// fairly unique strings. They are currently truncated md5 hashes of the time
|
||||
// plus a unique counter
|
||||
func (id *IdGenerator) Hash() string {
|
||||
h := md5.New()
|
||||
ns := time.Now().UnixNano() + <-id.id
|
||||
|
2
melee.go
2
melee.go
@ -4,6 +4,8 @@ import (
|
||||
"log"
|
||||
)
|
||||
|
||||
// melee is a game type that allows dead players to respond after a particular
|
||||
// number of ms.
|
||||
type melee struct {
|
||||
respawn map[*Robot]float64
|
||||
respawn_timer float64
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
v "bitbucket.org/hackerbots/vector"
|
||||
)
|
||||
|
||||
// Obstacle is the implementation of the generic building type in the game.
|
||||
type Obstacle struct {
|
||||
Bounds v.AABB2d `json:"bounds"`
|
||||
Hp int `json:"-"`
|
||||
@ -28,6 +29,8 @@ func (o Obstacle) minify() [4]int {
|
||||
return out
|
||||
}
|
||||
|
||||
// MinifyObstacles is a function used to convert []osbstacles into more tightly
|
||||
// packed [][4]int for smaller json payloads
|
||||
func MinifyObstacles(o []Obstacle) [][4]int {
|
||||
out := [][4]int{}
|
||||
for i := range o {
|
||||
@ -36,6 +39,8 @@ func MinifyObstacles(o []Obstacle) [][4]int {
|
||||
return out
|
||||
}
|
||||
|
||||
// GenerateObstacles returns a slice of (count) obstacles within a region
|
||||
// bounded by width, height.
|
||||
func GenerateObstacles(count int, width, height float32) []Obstacle {
|
||||
out := []Obstacle{}
|
||||
for i := 0; i < count; i++ {
|
||||
|
66
player.go
66
player.go
@ -19,39 +19,46 @@ type decoder interface {
|
||||
Decode(v interface{}) error
|
||||
}
|
||||
|
||||
type streamCounter struct {
|
||||
// StreamCount is the wrapper we use around reads and writes to keep track of
|
||||
// bandwidths.
|
||||
type StreamCount struct {
|
||||
ws *websocket.Conn
|
||||
bw *bandwidth.Bandwidth
|
||||
}
|
||||
|
||||
func (sc *streamCounter) Read(p []byte) (n int, err error) {
|
||||
// StreamCount.Read implements io.Reader
|
||||
func (sc *StreamCount) Read(p []byte) (n int, err error) {
|
||||
n, err = sc.ws.Read(p)
|
||||
sc.bw.AddRx <- n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (sc *streamCounter) Write(p []byte) (n int, err error) {
|
||||
// StreamCount.Write implements io.Writer
|
||||
func (sc *StreamCount) Write(p []byte) (n int, err error) {
|
||||
n, err = sc.ws.Write(p)
|
||||
sc.bw.AddTx <- n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (sc *streamCounter) Close() error {
|
||||
// Close is for cleanup
|
||||
func (sc *StreamCount) Close() error {
|
||||
return sc.ws.Close()
|
||||
}
|
||||
|
||||
type protoTalker struct {
|
||||
// ProtoTalker is the simplest form of struct that talks to consumers of the
|
||||
// service. There are two important methods here: Sender and Recv.
|
||||
type ProtoTalker struct {
|
||||
enc encoder
|
||||
dec decoder
|
||||
counter *streamCounter
|
||||
counter *StreamCount
|
||||
send chan Message
|
||||
Id string
|
||||
}
|
||||
|
||||
func NewProtoTalker(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, encoding string) *protoTalker {
|
||||
func NewProtoTalker(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, encoding string) *ProtoTalker {
|
||||
var enc encoder
|
||||
var dec decoder
|
||||
comptroller := &streamCounter{
|
||||
comptroller := &StreamCount{
|
||||
ws: ws,
|
||||
bw: bw,
|
||||
}
|
||||
@ -62,7 +69,7 @@ func NewProtoTalker(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, enco
|
||||
enc = gob.NewEncoder(comptroller)
|
||||
dec = gob.NewDecoder(comptroller)
|
||||
}
|
||||
return &protoTalker{
|
||||
return &ProtoTalker{
|
||||
send: make(chan Message, 16),
|
||||
enc: enc,
|
||||
dec: dec,
|
||||
@ -71,8 +78,10 @@ func NewProtoTalker(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, enco
|
||||
}
|
||||
}
|
||||
|
||||
func (pt *protoTalker) sender() {
|
||||
log.Printf("%s: %T sender launched", pt.Id, pt.enc)
|
||||
// Sender is the single implementation for data output to clients, both players
|
||||
// and spectators.
|
||||
func (pt *ProtoTalker) Sender() {
|
||||
log.Printf("%s: %T Sender launched", pt.Id, pt.enc)
|
||||
for things := range pt.send {
|
||||
err := pt.enc.Encode(things)
|
||||
if err != nil {
|
||||
@ -81,24 +90,28 @@ func (pt *protoTalker) sender() {
|
||||
}
|
||||
}
|
||||
pt.counter.Close()
|
||||
log.Printf("%s: sender close", pt.Id)
|
||||
log.Printf("%s: Sender close", pt.Id)
|
||||
}
|
||||
|
||||
type player struct {
|
||||
// player uses protoTalker's Sender method, but adds a Recv that knows how to
|
||||
// deal with game play instructions from the player.
|
||||
type Player struct {
|
||||
Robots []*Robot
|
||||
Instruction Instruction
|
||||
protoTalker
|
||||
ProtoTalker
|
||||
}
|
||||
|
||||
func NewPlayer(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, encoding string) *player {
|
||||
return &player{
|
||||
func NewPlayer(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, encoding string) *Player {
|
||||
return &Player{
|
||||
Robots: []*Robot{},
|
||||
protoTalker: *NewProtoTalker(id, ws, bw, encoding),
|
||||
ProtoTalker: *NewProtoTalker(id, ws, bw, encoding),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *player) recv() {
|
||||
log.Println("starting recv")
|
||||
// Player.Recv is the function responsible for parsing out player instructions
|
||||
// and sending them to the game.
|
||||
func (p *Player) Recv() {
|
||||
log.Println("starting Recv")
|
||||
for {
|
||||
var msgs map[string]Instruction
|
||||
err := p.dec.Decode(&msgs)
|
||||
@ -165,21 +178,26 @@ func (p *player) recv() {
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("%s: recv close", p.Id)
|
||||
log.Printf("%s: Recv close", p.Id)
|
||||
p.counter.Close()
|
||||
}
|
||||
|
||||
// Spectator merely sends out game state, does not receive meaningful
|
||||
// instructions from spectators.
|
||||
type Spectator struct {
|
||||
protoTalker
|
||||
ProtoTalker
|
||||
}
|
||||
|
||||
func NewSpectator(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, encoding string) *Spectator {
|
||||
return &Spectator{
|
||||
protoTalker: *NewProtoTalker(id, ws, bw, encoding),
|
||||
ProtoTalker: *NewProtoTalker(id, ws, bw, encoding),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Spectator) recv() {
|
||||
// Spectator.Recv is an interesting beast. We had to add it as for whatever
|
||||
// reason the server would lock up if we weren't reading the empty responses
|
||||
// from spectators.
|
||||
func (s *Spectator) Recv() {
|
||||
for {
|
||||
var msgs interface{}
|
||||
err := s.dec.Decode(&msgs)
|
||||
@ -196,6 +214,6 @@ func (s *Spectator) recv() {
|
||||
// break
|
||||
// }
|
||||
}
|
||||
log.Printf("%s: recv close", s.Id)
|
||||
log.Printf("%s: Recv close", s.Id)
|
||||
s.counter.Close()
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
v "bitbucket.org/hackerbots/vector"
|
||||
)
|
||||
|
||||
// Projectile are the things robots can shoot at eachother.
|
||||
type Projectile struct {
|
||||
Id string `json:"id"`
|
||||
Position v.Point2d `json:"position"`
|
||||
@ -17,6 +18,9 @@ type Projectile struct {
|
||||
Delta float32
|
||||
}
|
||||
|
||||
// Projectile.Tick is called every game tick and moves projectiles along,
|
||||
// determines when they should blow up, and how damaage is propagated to
|
||||
// players.
|
||||
func (p *Projectile) Tick(g *Game) {
|
||||
vec := p.MoveTo.Sub(p.Position)
|
||||
v_norm := vec.Normalize()
|
||||
|
42
protocol.go
42
protocol.go
@ -7,16 +7,15 @@ import (
|
||||
"code.google.com/p/go.net/websocket"
|
||||
)
|
||||
|
||||
// < the name of the game we want to join
|
||||
// GameID is essentially the name of the game we want to join
|
||||
type GameID struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
// > identify
|
||||
// PlayerID is the internal hash we give to a client
|
||||
type PlayerID struct {
|
||||
Type string `json:"type"`
|
||||
Hash string `json:"id"`
|
||||
Failure
|
||||
}
|
||||
|
||||
func NewPlayerID(id string) *PlayerID {
|
||||
@ -26,13 +25,15 @@ func NewPlayerID(id string) *PlayerID {
|
||||
}
|
||||
}
|
||||
|
||||
// < [robot | spectator], name, client-type, game ID
|
||||
// ClientID is how a player wants to be known
|
||||
type ClientID struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Useragent string `json:"useragent"`
|
||||
}
|
||||
|
||||
// ClientID.Valid is used to be sure the player connecting is of appropriate
|
||||
// type.
|
||||
func (c *ClientID) Valid() (bool, string) {
|
||||
switch c.Type {
|
||||
case "robot", "spectator":
|
||||
@ -41,11 +42,14 @@ func (c *ClientID) Valid() (bool, string) {
|
||||
return false, "useragent must be 'robot' or 'spectator'"
|
||||
}
|
||||
|
||||
// ClientConfig embodies a map of stats requests
|
||||
type ClientConfig struct {
|
||||
ID string `json:"id"`
|
||||
Stats map[string]StatsRequest `json:"stats"`
|
||||
}
|
||||
|
||||
// ClientConfig.Valid is what determins if a player has asked for too many
|
||||
// points.
|
||||
func (config ClientConfig) Valid(max int) bool {
|
||||
total := 0
|
||||
for _, s := range config.Stats {
|
||||
@ -65,11 +69,15 @@ func (config ClientConfig) Valid(max int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// BoardSize is the response containing the geometry of the requested game.
|
||||
type BoardSize struct {
|
||||
Width float32 `json:"width"`
|
||||
Height float32 `json:"height"`
|
||||
}
|
||||
|
||||
// GameParam is sent to the client to tell them of the geometry of the game
|
||||
// requested, how many points they may use, and what encoding the server will
|
||||
// use to communicate with the client.
|
||||
type GameParam struct {
|
||||
// TODO: should have information about max points in here
|
||||
BoardSize BoardSize `json:"boardsize"`
|
||||
@ -78,8 +86,8 @@ type GameParam struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// > [OK | FULL | NOT AUTH], board size, game params
|
||||
|
||||
// Handshake is simply the response to a client to let them know if the number
|
||||
// of stats they've asked for is reasonable. If false it means try again.
|
||||
type Handshake struct {
|
||||
ID string `json:"id"`
|
||||
Success bool `json:"success"`
|
||||
@ -94,9 +102,14 @@ func NewHandshake(id string, success bool) *Handshake {
|
||||
}
|
||||
}
|
||||
|
||||
type Message interface {
|
||||
}
|
||||
// Message is an empty interface used to send out arbitrary JSON/gob to
|
||||
// clients, both players/spectators. We might send out Boardstate or GameOver,
|
||||
// hence the interface{}.
|
||||
type Message interface{}
|
||||
|
||||
// Boardstate is the main struct calculated every tick (per player) and sent
|
||||
// out to clients. It contains the appropriate subset of all data needed by
|
||||
// each player/spectator.
|
||||
type Boardstate struct {
|
||||
MyRobots []Robot `json:"my_robots"`
|
||||
OtherRobots []OtherRobot `json:"robots"`
|
||||
@ -120,6 +133,7 @@ func NewBoardstate() *Boardstate {
|
||||
}
|
||||
}
|
||||
|
||||
// Special outbound message with a []string of winners.
|
||||
type GameOver struct {
|
||||
Winners []string `json:"winners"`
|
||||
Type string `json:"type"`
|
||||
@ -132,6 +146,8 @@ func NewGameOver() *GameOver {
|
||||
}
|
||||
}
|
||||
|
||||
// Failure is a simple stuct that is typically converted to JSON and sent out
|
||||
// to the clients so they can know why something has failed.
|
||||
type Failure struct {
|
||||
Reason string `json:"reason"`
|
||||
Type string `json:"type"`
|
||||
@ -144,6 +160,8 @@ func NewFailure(reason string) *Failure {
|
||||
}
|
||||
}
|
||||
|
||||
// Controller.AddPlayer is the HTTP -> websocket route that is used to
|
||||
// negociate a connection with a player/spectator.
|
||||
func (c *Controller) AddPlayer(ws *websocket.Conn) {
|
||||
var gid GameID
|
||||
err := websocket.JSON.Receive(ws, &gid)
|
||||
@ -324,9 +342,9 @@ encodingLoops:
|
||||
game.unregister <- p
|
||||
log.Printf("%s, %s: unregistered player", gid.Id, p.Id)
|
||||
}()
|
||||
go p.sender()
|
||||
go p.Sender()
|
||||
log.Printf("%s -> %s: p.sender went", gid.Id, p.Id)
|
||||
p.recv()
|
||||
p.Recv()
|
||||
log.Printf(
|
||||
"%s (player): %v (robot) has been disconnected from %s (game)",
|
||||
p.Id,
|
||||
@ -343,9 +361,9 @@ encodingLoops:
|
||||
game.sunregister <- s
|
||||
log.Printf("%s, %s: unregistered spectator", gid.Id, s.Id)
|
||||
}()
|
||||
go s.sender()
|
||||
go s.Sender()
|
||||
log.Printf("%s -> %s: s.sender went", gid.Id, s.Id)
|
||||
s.recv()
|
||||
s.Recv()
|
||||
log.Printf("game %s: spectator %+v has been disconnected from this game", gid.Id, s)
|
||||
}
|
||||
log.Printf("exiting AddPlayer")
|
||||
|
19
robot.go
19
robot.go
@ -8,6 +8,8 @@ import (
|
||||
v "bitbucket.org/hackerbots/vector"
|
||||
)
|
||||
|
||||
// Robot contains everything the game needs to know to simulate robot behavior.
|
||||
// Players have a []Robot
|
||||
type Robot struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@ -35,6 +37,7 @@ type Robot struct {
|
||||
idg *IdGenerator
|
||||
}
|
||||
|
||||
// Collision is basically a Point2d.
|
||||
type Collision struct {
|
||||
v.Point2d
|
||||
Type string `json:"type"`
|
||||
@ -50,6 +53,7 @@ type OtherRobot struct {
|
||||
Health int `json:"health"`
|
||||
}
|
||||
|
||||
// GetTruncatedDetails pares down our info into an OtherRobot.
|
||||
func (r Robot) GetTruncatedDetails() OtherRobot {
|
||||
return OtherRobot{
|
||||
Id: r.Id,
|
||||
@ -60,6 +64,7 @@ func (r Robot) GetTruncatedDetails() OtherRobot {
|
||||
}
|
||||
}
|
||||
|
||||
// RobotSorter implements sort.Interface for OtherRobot
|
||||
type RobotSorter struct {
|
||||
Robots []OtherRobot
|
||||
}
|
||||
@ -76,7 +81,7 @@ func (s RobotSorter) Less(i, j int) bool {
|
||||
return s.Robots[i].Id < s.Robots[j].Id
|
||||
}
|
||||
|
||||
// TODO - how do I not duplicate this code???
|
||||
// AllRobotSorter implements sort.Inteface for BotHealth
|
||||
type AllRobotSorter struct {
|
||||
Robots []BotHealth
|
||||
}
|
||||
@ -93,6 +98,7 @@ func (s AllRobotSorter) Less(i, j int) bool {
|
||||
return s.Robots[i].RobotId < s.Robots[j].RobotId
|
||||
}
|
||||
|
||||
// Stats is the point allocation for a Robot.
|
||||
type Stats struct {
|
||||
Hp int `json:"hp"`
|
||||
Speed float32 `json:"speed"`
|
||||
@ -105,8 +111,9 @@ type Stats struct {
|
||||
WeaponSpeed float32 `json:"weapon_speed"`
|
||||
}
|
||||
|
||||
// We request stats using an integer between 1 and 100, the
|
||||
// integer values map to sensible min-max ranges
|
||||
// StatsRequest is the struct used in comunication with the player. We request
|
||||
// stats using an integer between 1 and 100, the integer values map to sensible
|
||||
// min-max ranges
|
||||
type StatsRequest struct {
|
||||
Hp int `json:"hp"`
|
||||
Speed int `json:"speed"`
|
||||
@ -119,6 +126,7 @@ type StatsRequest struct {
|
||||
WeaponSpeed int `json:"weapon_speed"`
|
||||
}
|
||||
|
||||
// DeriveStats maps the 0-100 values to sensible in-game min-max values.
|
||||
func DeriveStats(request StatsRequest) Stats {
|
||||
s := Stats{}
|
||||
|
||||
@ -162,6 +170,7 @@ func DeriveStats(request StatsRequest) Stats {
|
||||
return s
|
||||
}
|
||||
|
||||
// Instruction is the struct a player sends each turn.
|
||||
type Instruction struct {
|
||||
Message *string `json:"message,omitempty"`
|
||||
MoveTo *v.Point2d `json:"move_to,omitempty"`
|
||||
@ -246,6 +255,7 @@ func (r *Robot) checkCollisions(g *Game, probe v.Vector2d) (bool, *v.Point2d, *R
|
||||
return finalCollision, intersection, finalRobot
|
||||
}
|
||||
|
||||
// Tick is the Robot's chance to udpate itself.
|
||||
func (r *Robot) Tick(g *Game) {
|
||||
r.Collision = nil
|
||||
r.Hit = false
|
||||
@ -397,6 +407,7 @@ func (r *Robot) Tick(g *Game) {
|
||||
}
|
||||
}
|
||||
|
||||
// scan updates the robots field of view if it's in teh appropriate mode
|
||||
func (r *Robot) scan(g *Game) {
|
||||
r.Scanners = r.Scanners[:0]
|
||||
for player := range g.players {
|
||||
@ -443,6 +454,7 @@ func (r *Robot) scan(g *Game) {
|
||||
|
||||
}
|
||||
|
||||
// fire is called according to player instruction. XXX: There is a race here...
|
||||
func (r *Robot) fire(g *Game) *Projectile {
|
||||
// Throttle the fire rate
|
||||
time_since_fired := (float32(g.turn) * (r.Delta * 1000)) - (float32(r.LastFired) * (r.Delta * 1000))
|
||||
@ -465,6 +477,7 @@ func (r *Robot) fire(g *Game) *Projectile {
|
||||
}
|
||||
}
|
||||
|
||||
// reset is called to move a robot to a reasonable location at game start time.
|
||||
func (r *Robot) reset(g *Game) {
|
||||
for {
|
||||
start_pos := v.Point2d{
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
v "bitbucket.org/hackerbots/vector"
|
||||
)
|
||||
|
||||
// Splosion embodies an explosion.
|
||||
type Splosion struct {
|
||||
Id string `json:"id"`
|
||||
Position v.Point2d `json:"position"`
|
||||
@ -11,10 +12,12 @@ type Splosion struct {
|
||||
Lifespan int `json:"-"`
|
||||
}
|
||||
|
||||
// Tick decrements the lifespan of said Splosion.
|
||||
func (s *Splosion) Tick() {
|
||||
s.Lifespan--
|
||||
}
|
||||
|
||||
// Alive determines if this Splosion is still relevant.
|
||||
func (s *Splosion) Alive() bool {
|
||||
return s.Lifespan > 0
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user