No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

protocol.go 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. package botserv
  2. import (
  3. "log"
  4. v "bitbucket.org/hackerbots/vector"
  5. "code.google.com/p/go.net/websocket"
  6. )
  7. // GameID is essentially the name of the game we want to join
  8. type GameID struct {
  9. Id string `json:"id"`
  10. }
  11. // PlayerID is the internal hash we give to a client
  12. type PlayerID struct {
  13. Type string `json:"type"`
  14. Hash string `json:"id"`
  15. }
  16. func NewPlayerID(id string) *PlayerID {
  17. return &PlayerID{
  18. Type: "idreq",
  19. Hash: id,
  20. }
  21. }
  22. // ClientID is how a player wants to be known
  23. type ClientID struct {
  24. Type string `json:"type"`
  25. Name string `json:"name"`
  26. Useragent string `json:"useragent"`
  27. }
  28. // ClientID.Valid is used to be sure the player connecting is of appropriate
  29. // type.
  30. func (c *ClientID) Valid() (bool, string) {
  31. switch c.Type {
  32. case "robot", "spectator":
  33. return true, ""
  34. }
  35. return false, "useragent must be 'robot' or 'spectator'"
  36. }
  37. // ClientConfig embodies a map of stats requests
  38. type ClientConfig struct {
  39. ID string `json:"id"`
  40. Stats map[string]StatsRequest `json:"stats"`
  41. }
  42. // ClientConfig.Valid is what determins if a player has asked for too many
  43. // points.
  44. func (config ClientConfig) Valid(max int) bool {
  45. total := 0
  46. for _, s := range config.Stats {
  47. total += (s.Speed +
  48. s.Hp +
  49. s.WeaponRadius +
  50. s.ScannerRadius +
  51. s.Acceleration +
  52. s.TurnSpeed +
  53. s.FireRate +
  54. s.WeaponDamage +
  55. s.WeaponSpeed)
  56. }
  57. if total > max {
  58. return false
  59. }
  60. return true
  61. }
  62. // BoardSize is the response containing the geometry of the requested game.
  63. type BoardSize struct {
  64. Width float32 `json:"width"`
  65. Height float32 `json:"height"`
  66. }
  67. // GameParam is sent to the client to tell them of the geometry of the game
  68. // requested, how many points they may use, and what encoding the server will
  69. // use to communicate with the client.
  70. type GameParam struct {
  71. // TODO: should have information about max points in here
  72. BoardSize BoardSize `json:"boardsize"`
  73. MaxPoints int `json:"max_points"`
  74. Encoding string `json:"encoding"`
  75. Type string `json:"type"`
  76. }
  77. // Handshake is simply the response to a client to let them know if the number
  78. // of stats they've asked for is reasonable. If false it means try again.
  79. type Handshake struct {
  80. ID string `json:"id"`
  81. Success bool `json:"success"`
  82. Type string `json:"type"`
  83. }
  84. func NewHandshake(id string, success bool) *Handshake {
  85. return &Handshake{
  86. ID: id,
  87. Success: success,
  88. Type: "handshake",
  89. }
  90. }
  91. // Message is an empty interface used to send out arbitrary JSON/gob to
  92. // clients, both players/spectators. We might send out Boardstate or GameOver,
  93. // hence the interface{}.
  94. type Message interface{}
  95. // Boardstate is the main struct calculated every tick (per player) and sent
  96. // out to clients. It contains the appropriate subset of all data needed by
  97. // each player/spectator.
  98. type Boardstate struct {
  99. MyRobots []Robot `json:"my_robots"`
  100. OtherRobots []OtherRobot `json:"robots"`
  101. Projectiles []Projectile `json:"projectiles"`
  102. Splosions []Splosion `json:"splosions"`
  103. Objects [][4]int `json:"objects"`
  104. Type string `json:"type"`
  105. Turn int `json:"turn"`
  106. AllBots []BotHealth `json:"all_bots"`
  107. Messages []string `json:"messages"`
  108. }
  109. func NewBoardstate() *Boardstate {
  110. return &Boardstate{
  111. MyRobots: []Robot{},
  112. OtherRobots: []OtherRobot{},
  113. Projectiles: []Projectile{},
  114. Splosions: []Splosion{},
  115. AllBots: []BotHealth{},
  116. Type: "boardstate",
  117. }
  118. }
  119. // Special outbound message with a []string of winners.
  120. type GameOver struct {
  121. Winners []string `json:"winners"`
  122. Type string `json:"type"`
  123. }
  124. func NewGameOver() *GameOver {
  125. return &GameOver{
  126. Type: "gameover",
  127. Winners: make([]string, 0),
  128. }
  129. }
  130. // Failure is a simple stuct that is typically converted to JSON and sent out
  131. // to the clients so they can know why something has failed.
  132. type Failure struct {
  133. Reason string `json:"reason"`
  134. Type string `json:"type"`
  135. }
  136. func NewFailure(reason string) *Failure {
  137. return &Failure{
  138. Reason: reason,
  139. Type: "failure",
  140. }
  141. }
  142. // Controller.AddPlayer is the HTTP -> websocket route that is used to
  143. // negociate a connection with a player/spectator.
  144. func (c *Controller) AddPlayer(ws *websocket.Conn) {
  145. var gid GameID
  146. err := websocket.JSON.Receive(ws, &gid)
  147. if err != nil {
  148. log.Println("problem parsing the requested game id")
  149. return
  150. }
  151. game := c.Games.Get(gid.Id)
  152. if game == nil {
  153. var err error
  154. game, err = NewGame(
  155. gid.Id,
  156. float32(c.Conf.Width),
  157. float32(c.Conf.Height),
  158. c.Conf.Obstacles,
  159. c.Conf.Tick,
  160. c.Conf.MaxPoints,
  161. "",
  162. )
  163. if err != nil {
  164. log.Printf("problem creating game: %s", gid.Id)
  165. websocket.JSON.Send(ws, NewFailure("game creation error"))
  166. return
  167. }
  168. go game.run()
  169. c.Games.Add(game)
  170. }
  171. player_id := c.Idg.Hash()
  172. err = websocket.JSON.Send(ws, NewPlayerID(player_id))
  173. if err != nil {
  174. log.Printf("game %s: unable to send player_id to player %s", gid.Id, player_id)
  175. websocket.JSON.Send(ws, NewFailure("send error"))
  176. return
  177. } else {
  178. log.Printf("game %s: sent player id: %s", gid.Id, player_id)
  179. }
  180. var clientid ClientID
  181. err = websocket.JSON.Receive(ws, &clientid)
  182. if err != nil {
  183. log.Printf("unable to parse ClientID: gid: %s, player: %s", gid.Id, player_id)
  184. websocket.JSON.Send(ws, NewFailure("parse error"))
  185. return
  186. } else {
  187. log.Printf("game %s: recieved: %+v", gid.Id, clientid)
  188. }
  189. if v, msg := clientid.Valid(); !v {
  190. log.Printf("clientid is invalid: %+v", clientid)
  191. websocket.JSON.Send(
  192. ws,
  193. NewFailure(msg),
  194. )
  195. return
  196. }
  197. reqEncs := []string{}
  198. err = websocket.JSON.Receive(ws, &reqEncs)
  199. if err != nil {
  200. log.Printf("%s %s unable to parse requested encodings", gid.Id, player_id)
  201. websocket.JSON.Send(ws, NewFailure("encoding recieve error"))
  202. return
  203. }
  204. prefEncs := []string{
  205. "gob",
  206. "json",
  207. }
  208. var encoding string
  209. encodingLoops:
  210. for _, prefEnc := range prefEncs {
  211. for _, reqEnc := range reqEncs {
  212. if reqEnc == prefEnc {
  213. encoding = prefEnc
  214. log.Println("selected following encoding:", encoding)
  215. break encodingLoops
  216. }
  217. }
  218. }
  219. if encoding == "" {
  220. log.Printf("%s %s unable to negociate encoding", gid.Id, player_id)
  221. websocket.JSON.Send(
  222. ws,
  223. NewFailure("no overlap on supported encodings; I suggest using json"),
  224. )
  225. return
  226. }
  227. gameParam := game.gameParam()
  228. gp := struct {
  229. GameParam
  230. Encoding string `json:"encoding"`
  231. }{
  232. GameParam: *gameParam,
  233. Encoding: encoding,
  234. }
  235. err = websocket.JSON.Send(ws, gp)
  236. if err != nil {
  237. log.Printf("%s %s game param send error", gid.Id, player_id)
  238. websocket.JSON.Send(ws, NewFailure("game param send error"))
  239. return
  240. } else {
  241. log.Printf("%s -> %s: sent %+v", gid.Id, player_id, gameParam)
  242. }
  243. switch clientid.Type {
  244. case "robot":
  245. var conf ClientConfig
  246. for {
  247. log.Printf("%s Waiting for client to send conf ...", player_id)
  248. err = websocket.JSON.Receive(ws, &conf)
  249. log.Printf("%s: conf received: %s", player_id, conf.ID)
  250. if err != nil {
  251. log.Printf("%s %s config parse error", gid.Id, player_id)
  252. websocket.JSON.Send(ws, NewFailure("config parse error"))
  253. return
  254. }
  255. // TODO: verify conf's type
  256. if conf.Valid(game.maxPoints) {
  257. log.Printf("%s -> %s: valid client config", gid.Id, player_id)
  258. _ = websocket.JSON.Send(ws, NewHandshake(player_id, true))
  259. break
  260. } else {
  261. log.Printf("%s: Config is INVALID, abort", player_id)
  262. _ = websocket.JSON.Send(ws, NewFailure("invalid config"))
  263. return
  264. }
  265. }
  266. p := NewPlayer(player_id, ws, game.bw, encoding)
  267. log.Printf("%s: made a player: %s", gid.Id, p.Id)
  268. convertedStats := map[string]Stats{}
  269. for name, stats := range conf.Stats {
  270. dstat := DeriveStats(stats)
  271. convertedStats[name] = dstat
  272. r := Robot{
  273. Stats: dstat,
  274. Id: c.Idg.Hash(),
  275. Name: name,
  276. Health: 10,
  277. Heading: v.Vector2d{X: 1, Y: 0},
  278. Scanners: make([]Scanner, 0),
  279. Delta: c.Conf.Delta,
  280. idg: c.Idg,
  281. }
  282. r.Health = r.Stats.Hp
  283. log.Printf("%s: adding robot: %s", p.Id, r.Id)
  284. r.reset(game)
  285. p.Robots = append(p.Robots, &r)
  286. }
  287. statsPayload := struct {
  288. Stats map[string]Stats `json:"stats"`
  289. Type string `json:"type"`
  290. }{
  291. Stats: convertedStats,
  292. Type: "stats",
  293. }
  294. err = websocket.JSON.Send(ws, &statsPayload)
  295. if err != nil {
  296. log.Printf("error sending convertedStats to client: %s", err)
  297. websocket.JSON.Send(ws, NewFailure("protocol error: convertedStats"))
  298. return
  299. } else {
  300. log.Printf("%s -> %s: sent stats payload", gid.Id, p.Id)
  301. }
  302. log.Printf("%s, %s: about to register this player", gid.Id, p.Id)
  303. game.register <- p
  304. log.Printf("%s, %s: registered player", gid.Id, p.Id)
  305. defer func() {
  306. log.Printf("%s, %s: about to unregister this player", gid.Id, p.Id)
  307. game.unregister <- p
  308. log.Printf("%s, %s: unregistered player", gid.Id, p.Id)
  309. }()
  310. go p.Sender()
  311. log.Printf("%s -> %s: p.sender went", gid.Id, p.Id)
  312. p.Recv()
  313. log.Printf(
  314. "%s (player): %v (robot) has been disconnected from %s (game)",
  315. p.Id,
  316. p.Robots[0].Id,
  317. gid.Id,
  318. )
  319. case "spectator":
  320. s := NewSpectator(player_id, ws, game.bw, encoding)
  321. log.Printf("%s, %s: about to register this spectator", gid.Id, s.Id)
  322. game.sregister <- s
  323. log.Printf("%s, %s: registered spectator", gid.Id, s.Id)
  324. defer func() {
  325. log.Printf("%s, %s: about to unregister this spectator", gid.Id, s.Id)
  326. game.sunregister <- s
  327. log.Printf("%s, %s: unregistered spectator", gid.Id, s.Id)
  328. }()
  329. go s.Sender()
  330. log.Printf("%s -> %s: s.sender went", gid.Id, s.Id)
  331. s.Recv()
  332. log.Printf("game %s: spectator %+v has been disconnected from this game", gid.Id, s)
  333. }
  334. log.Printf("exiting AddPlayer")
  335. }