Stephen McQuay
9a4b76f610
This finalizes the work on getting the server to support multiple encodings for players and spectators.
348 lines
8.0 KiB
Go
348 lines
8.0 KiB
Go
package main
|
|
|
|
import (
|
|
"log"
|
|
|
|
v "bitbucket.org/hackerbots/vector"
|
|
"code.google.com/p/go.net/websocket"
|
|
)
|
|
|
|
// < the name of the game we want to join
|
|
type GameID struct {
|
|
Id string `json:"id"`
|
|
}
|
|
|
|
// > identify
|
|
type PlayerID struct {
|
|
Type string `json:"type"`
|
|
Hash string `json:"id"`
|
|
Failure
|
|
}
|
|
|
|
func NewPlayerID(id string) *PlayerID {
|
|
return &PlayerID{
|
|
Type: "idreq",
|
|
Hash: id,
|
|
}
|
|
}
|
|
|
|
// < [robot | spectator], name, client-type, game ID
|
|
type ClientID struct {
|
|
Type string `json:"type"`
|
|
Name string `json:"name"`
|
|
Useragent string `json:"useragent"`
|
|
}
|
|
|
|
func (c *ClientID) Valid() (bool, string) {
|
|
switch c.Type {
|
|
case "robot", "spectator":
|
|
return true, ""
|
|
}
|
|
return false, "useragent must be 'robot' or 'spectator'"
|
|
}
|
|
|
|
type ClientConfig struct {
|
|
ID string `json:"id"`
|
|
Stats map[string]StatsRequest `json:"stats"`
|
|
}
|
|
|
|
func (config ClientConfig) Valid(max int) bool {
|
|
total := 0
|
|
for _, s := range config.Stats {
|
|
total += (s.Speed +
|
|
s.Hp +
|
|
s.WeaponRadius +
|
|
s.ScannerRadius +
|
|
s.Acceleration +
|
|
s.TurnSpeed +
|
|
s.FireRate +
|
|
s.WeaponDamage +
|
|
s.WeaponSpeed)
|
|
}
|
|
if total > max {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
type BoardSize struct {
|
|
Width float32 `json:"width"`
|
|
Height float32 `json:"height"`
|
|
}
|
|
|
|
type GameParam struct {
|
|
// TODO: should have information about max points in here
|
|
BoardSize BoardSize `json:"boardsize"`
|
|
MaxPoints int `json:"max_points"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
// > [OK | FULL | NOT AUTH], board size, game params
|
|
|
|
type Handshake struct {
|
|
ID string `json:"id"`
|
|
Success bool `json:"success"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
func NewHandshake(id string, success bool) *Handshake {
|
|
return &Handshake{
|
|
ID: id,
|
|
Success: success,
|
|
Type: "handshake",
|
|
}
|
|
}
|
|
|
|
type Message interface {
|
|
}
|
|
|
|
type Boardstate struct {
|
|
MyRobots []Robot `json:"my_robots"`
|
|
OtherRobots []OtherRobot `json:"robots"`
|
|
Projectiles []Projectile `json:"projectiles"`
|
|
Splosions []Splosion `json:"splosions"`
|
|
Objects [][4]int `json:"objects"`
|
|
Type string `json:"type"`
|
|
Turn int `json:"turn"`
|
|
AllBots []BotHealth `json:"all_bots"`
|
|
Messages []string `json:"messages"`
|
|
}
|
|
|
|
func NewBoardstate() *Boardstate {
|
|
return &Boardstate{
|
|
MyRobots: []Robot{},
|
|
OtherRobots: []OtherRobot{},
|
|
Projectiles: []Projectile{},
|
|
Splosions: []Splosion{},
|
|
AllBots: []BotHealth{},
|
|
Type: "boardstate",
|
|
}
|
|
}
|
|
|
|
type GameOver struct {
|
|
Winners []string `json:"winners"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
func NewGameOver() *GameOver {
|
|
return &GameOver{
|
|
Type: "gameover",
|
|
Winners: make([]string, 0),
|
|
}
|
|
}
|
|
|
|
type Failure struct {
|
|
Reason string `json:"reason"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
func NewFailure(reason string) *Failure {
|
|
return &Failure{
|
|
Reason: reason,
|
|
Type: "failure",
|
|
}
|
|
}
|
|
|
|
func addPlayer(ws *websocket.Conn) {
|
|
var gid GameID
|
|
err := websocket.JSON.Receive(ws, &gid)
|
|
if err != nil {
|
|
log.Println("problem parsing the requested game id")
|
|
return
|
|
}
|
|
|
|
game := games.get(gid.Id)
|
|
if game == nil {
|
|
var err error
|
|
game, err = NewGame(
|
|
gid.Id,
|
|
float32(conf.Width),
|
|
float32(conf.Height),
|
|
conf.Obstacles,
|
|
conf.Tick,
|
|
conf.MaxPoints,
|
|
"",
|
|
)
|
|
if err != nil {
|
|
log.Printf("problem creating game: %s", gid.Id)
|
|
websocket.JSON.Send(ws, NewFailure("game creation error"))
|
|
return
|
|
}
|
|
go game.run()
|
|
games.add(game)
|
|
}
|
|
|
|
player_id := idg.Hash()
|
|
err = websocket.JSON.Send(ws, NewPlayerID(player_id))
|
|
if err != nil {
|
|
log.Printf("game %s: unable to send player_id to player %s", gid.Id, player_id)
|
|
websocket.JSON.Send(ws, NewFailure("send error"))
|
|
return
|
|
} else {
|
|
log.Printf("game %s: sent player id: %s", gid.Id, player_id)
|
|
}
|
|
|
|
var clientid ClientID
|
|
err = websocket.JSON.Receive(ws, &clientid)
|
|
if err != nil {
|
|
log.Printf("unable to parse ClientID: gid: %s, player: %s", gid.Id, player_id)
|
|
websocket.JSON.Send(ws, NewFailure("parse error"))
|
|
return
|
|
} else {
|
|
log.Printf("game %s: recieved: %+v", gid.Id, clientid)
|
|
}
|
|
if v, msg := clientid.Valid(); !v {
|
|
log.Printf("clientid is invalid: %+v", clientid)
|
|
websocket.JSON.Send(
|
|
ws,
|
|
NewFailure(msg),
|
|
)
|
|
return
|
|
}
|
|
|
|
reqEncs := []string{}
|
|
err = websocket.JSON.Receive(ws, &reqEncs)
|
|
if err != nil {
|
|
log.Printf("%s %s unable to parse requested encodings", gid.Id, player_id)
|
|
websocket.JSON.Send(ws, NewFailure("encoding recieve error"))
|
|
return
|
|
}
|
|
prefEncs := []string{
|
|
"gob",
|
|
"json",
|
|
}
|
|
|
|
var encoding string
|
|
encodingLoops:
|
|
for _, prefEnc := range prefEncs {
|
|
for _, reqEnc := range reqEncs {
|
|
if reqEnc == prefEnc {
|
|
encoding = prefEnc
|
|
log.Println("selected following encoding:", encoding)
|
|
break encodingLoops
|
|
}
|
|
}
|
|
}
|
|
if encoding == "" {
|
|
log.Printf("%s %s unable to negociate encoding", gid.Id, player_id)
|
|
websocket.JSON.Send(
|
|
ws,
|
|
NewFailure("no overlap on supported encodings; I suggest using json"),
|
|
)
|
|
return
|
|
}
|
|
|
|
gameParam := game.gameParam()
|
|
gp := struct {
|
|
GameParam
|
|
Encoding string `json:"encoding"`
|
|
}{
|
|
GameParam: *gameParam,
|
|
Encoding: encoding,
|
|
}
|
|
err = websocket.JSON.Send(ws, gp)
|
|
if err != nil {
|
|
log.Printf("%s %s game param send error", gid.Id, player_id)
|
|
websocket.JSON.Send(ws, NewFailure("game param send error"))
|
|
return
|
|
} else {
|
|
log.Printf("%s -> %s: sent %+v", gid.Id, player_id, gameParam)
|
|
}
|
|
|
|
switch clientid.Type {
|
|
case "robot":
|
|
var conf ClientConfig
|
|
for {
|
|
log.Printf("%s Waiting for client to send conf ...", player_id)
|
|
err = websocket.JSON.Receive(ws, &conf)
|
|
log.Printf("%s: conf received: %s", player_id, conf.ID)
|
|
|
|
if err != nil {
|
|
log.Printf("%s %s config parse error", gid.Id, player_id)
|
|
websocket.JSON.Send(ws, NewFailure("config parse error"))
|
|
return
|
|
}
|
|
|
|
// TODO: verify conf's type
|
|
if conf.Valid(game.maxPoints) {
|
|
log.Printf("%s -> %s: valid client config", gid.Id, player_id)
|
|
_ = websocket.JSON.Send(ws, NewHandshake(player_id, true))
|
|
break
|
|
} else {
|
|
log.Printf("%s: Config is INVALID, abort", player_id)
|
|
_ = websocket.JSON.Send(ws, NewFailure("invalid config"))
|
|
return
|
|
}
|
|
}
|
|
|
|
p := NewPlayer(player_id, ws, game.bw, encoding)
|
|
log.Printf("%s: made a player: %s", gid.Id, p.Id)
|
|
|
|
convertedStats := map[string]Stats{}
|
|
for name, stats := range conf.Stats {
|
|
dstat := DeriveStats(stats)
|
|
convertedStats[name] = dstat
|
|
r := Robot{
|
|
Stats: dstat,
|
|
Id: idg.Hash(),
|
|
Name: name,
|
|
Health: 10,
|
|
Heading: v.Vector2d{1, 0},
|
|
Scanners: make([]Scanner, 0)}
|
|
r.Health = r.Stats.Hp
|
|
log.Printf("%s: adding robot: %s", p.Id, r.Id)
|
|
r.reset(game)
|
|
p.Robots = append(p.Robots, &r)
|
|
}
|
|
|
|
statsPayload := struct {
|
|
Stats map[string]Stats `json:"stats"`
|
|
Type string `json:"type"`
|
|
}{
|
|
Stats: convertedStats,
|
|
Type: "stats",
|
|
}
|
|
err = websocket.JSON.Send(ws, &statsPayload)
|
|
if err != nil {
|
|
log.Printf("error sending convertedStats to client: %s", err)
|
|
websocket.JSON.Send(ws, NewFailure("protocol error: convertedStats"))
|
|
return
|
|
} else {
|
|
log.Printf("%s -> %s: sent stats payload", gid.Id, p.Id)
|
|
}
|
|
|
|
log.Printf("%s, %s: about to register this player", gid.Id, p.Id)
|
|
game.register <- p
|
|
log.Printf("%s, %s: registered player", gid.Id, p.Id)
|
|
|
|
defer func() {
|
|
log.Printf("%s, %s: about to unregister this player", gid.Id, p.Id)
|
|
game.unregister <- p
|
|
log.Printf("%s, %s: unregistered player", gid.Id, p.Id)
|
|
}()
|
|
go p.sender()
|
|
log.Printf("%s -> %s: p.sender went", gid.Id, p.Id)
|
|
p.recv()
|
|
log.Printf(
|
|
"%s (player): %v (robot) has been disconnected from %s (game)",
|
|
p.Id,
|
|
p.Robots[0].Id,
|
|
gid.Id,
|
|
)
|
|
case "spectator":
|
|
s := NewSpectator(player_id, ws, game.bw, encoding)
|
|
log.Printf("%s, %s: about to register this spectator", gid.Id, s.Id)
|
|
game.sregister <- s
|
|
log.Printf("%s, %s: registered spectator", gid.Id, s.Id)
|
|
defer func() {
|
|
log.Printf("%s, %s: about to unregister this spectator", gid.Id, s.Id)
|
|
game.sunregister <- s
|
|
log.Printf("%s, %s: unregistered spectator", gid.Id, s.Id)
|
|
}()
|
|
go s.sender()
|
|
log.Printf("%s -> %s: s.sender went", gid.Id, s.Id)
|
|
s.recv()
|
|
log.Printf("game %s: spectator %+v has been disconnected from this game", gid.Id, s)
|
|
}
|
|
}
|