Nenhuma descrição
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

protocol.go 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. package server
  2. import (
  3. "log"
  4. "golang.org/x/net/websocket"
  5. v "hackerbots.us/vector"
  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 float64 `json:"width"`
  65. Height float64 `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. Obstacles []Obstacle `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. float64(c.Conf.Width),
  157. float64(c.Conf.Height),
  158. c.Conf.Tick,
  159. c.Conf.MaxPoints,
  160. "",
  161. )
  162. game.defaultObstacles = c.Conf.Obstacles
  163. game.obstacleCount = c.Conf.ObstacleCount
  164. log.Printf("%t", len(game.defaultObstacles) == 0)
  165. if len(game.defaultObstacles) == 0 {
  166. game.obstacles = GenerateObstacles(
  167. game.obstacleCount,
  168. game.width,
  169. game.height,
  170. )
  171. } else {
  172. game.obstacles = c.Conf.Obstacles
  173. }
  174. if err != nil {
  175. log.Printf("problem creating game: %s", gid.Id)
  176. websocket.JSON.Send(ws, NewFailure("game creation error"))
  177. return
  178. }
  179. go game.run()
  180. c.Games.Add(game)
  181. }
  182. player_id := c.Idg.Hash()
  183. err = websocket.JSON.Send(ws, NewPlayerID(player_id))
  184. if err != nil {
  185. log.Printf("game %s: unable to send player_id to player %s", gid.Id, player_id)
  186. websocket.JSON.Send(ws, NewFailure("send error"))
  187. return
  188. } else {
  189. log.Printf("game %s: sent player id: %s", gid.Id, player_id)
  190. }
  191. var clientid ClientID
  192. err = websocket.JSON.Receive(ws, &clientid)
  193. if err != nil {
  194. log.Printf("unable to parse ClientID: gid: %s, player: %s", gid.Id, player_id)
  195. websocket.JSON.Send(ws, NewFailure("parse error"))
  196. return
  197. } else {
  198. log.Printf("game %s: recieved: %+v", gid.Id, clientid)
  199. }
  200. if v, msg := clientid.Valid(); !v {
  201. log.Printf("clientid is invalid: %+v", clientid)
  202. websocket.JSON.Send(
  203. ws,
  204. NewFailure(msg),
  205. )
  206. return
  207. }
  208. reqEncs := []string{}
  209. err = websocket.JSON.Receive(ws, &reqEncs)
  210. if err != nil {
  211. log.Printf("%s %s unable to parse requested encodings", gid.Id, player_id)
  212. websocket.JSON.Send(ws, NewFailure("encoding recieve error"))
  213. return
  214. }
  215. prefEncs := []string{
  216. "gob",
  217. "json",
  218. }
  219. var encoding string
  220. encodingLoops:
  221. for _, prefEnc := range prefEncs {
  222. for _, reqEnc := range reqEncs {
  223. if reqEnc == prefEnc {
  224. encoding = prefEnc
  225. log.Println("selected following encoding:", encoding)
  226. break encodingLoops
  227. }
  228. }
  229. }
  230. if encoding == "" {
  231. log.Printf("%s %s unable to negociate encoding", gid.Id, player_id)
  232. websocket.JSON.Send(
  233. ws,
  234. NewFailure("no overlap on supported encodings; I suggest using json"),
  235. )
  236. return
  237. }
  238. gameParam := game.gameParam()
  239. gp := struct {
  240. GameParam
  241. Encoding string `json:"encoding"`
  242. }{
  243. GameParam: *gameParam,
  244. Encoding: encoding,
  245. }
  246. err = websocket.JSON.Send(ws, gp)
  247. if err != nil {
  248. log.Printf("%s %s game param send error", gid.Id, player_id)
  249. websocket.JSON.Send(ws, NewFailure("game param send error"))
  250. return
  251. } else {
  252. log.Printf("%s -> %s: sent %+v", gid.Id, player_id, gameParam)
  253. }
  254. switch clientid.Type {
  255. case "robot":
  256. var conf ClientConfig
  257. for {
  258. log.Printf("%s Waiting for client to send conf ...", player_id)
  259. err = websocket.JSON.Receive(ws, &conf)
  260. log.Printf("%s: conf received: %s", player_id, conf.ID)
  261. if err != nil {
  262. log.Printf("%s %s config parse error", gid.Id, player_id)
  263. websocket.JSON.Send(ws, NewFailure("config parse error"))
  264. return
  265. }
  266. // TODO: verify conf's type
  267. if conf.Valid(game.maxPoints) {
  268. log.Printf("%s -> %s: valid client config", gid.Id, player_id)
  269. _ = websocket.JSON.Send(ws, NewHandshake(player_id, true))
  270. break
  271. } else {
  272. log.Printf("%s: Config is INVALID, abort", player_id)
  273. _ = websocket.JSON.Send(ws, NewFailure("invalid config"))
  274. return
  275. }
  276. }
  277. p := NewPlayer(player_id, ws, game.bw, encoding)
  278. log.Printf("%s: made a player: %s", gid.Id, p.Id)
  279. convertedStats := map[string]Stats{}
  280. for name, stats := range conf.Stats {
  281. dstat := DeriveStats(stats)
  282. r := Robot{
  283. Stats: dstat,
  284. Id: c.Idg.Hash(),
  285. Name: name,
  286. Health: 10,
  287. Heading: v.Vector2d{X: 1, Y: 0},
  288. Scanners: make([]Scanner, 0),
  289. Delta: c.Conf.Delta,
  290. idg: c.Idg,
  291. }
  292. r.Health = r.Stats.Hp
  293. log.Printf("%s: adding robot: %s", p.Id, r.Id)
  294. r.reset(game)
  295. p.Robots = append(p.Robots, &r)
  296. dstat.Id = r.Id
  297. convertedStats[name] = dstat
  298. }
  299. statsPayload := struct {
  300. Stats map[string]Stats `json:"stats"`
  301. Type string `json:"type"`
  302. }{
  303. Stats: convertedStats,
  304. Type: "stats",
  305. }
  306. err = websocket.JSON.Send(ws, &statsPayload)
  307. if err != nil {
  308. log.Printf("error sending convertedStats to client: %s", err)
  309. websocket.JSON.Send(ws, NewFailure("protocol error: convertedStats"))
  310. return
  311. } else {
  312. log.Printf("%s -> %s: sent stats payload", gid.Id, p.Id)
  313. }
  314. log.Printf("%s, %s: about to register this player", gid.Id, p.Id)
  315. game.register <- p
  316. log.Printf("%s, %s: registered player", gid.Id, p.Id)
  317. defer func() {
  318. log.Printf("%s, %s: about to unregister this player", gid.Id, p.Id)
  319. game.unregister <- p
  320. log.Printf("%s, %s: unregistered player", gid.Id, p.Id)
  321. }()
  322. go p.Sender()
  323. log.Printf("%s -> %s: p.sender went", gid.Id, p.Id)
  324. p.Recv()
  325. log.Printf(
  326. "%s (player): %v (robot) has been disconnected from %s (game)",
  327. p.Id,
  328. p.Robots[0].Id,
  329. gid.Id,
  330. )
  331. case "spectator":
  332. s := NewSpectator(player_id, ws, game.bw, encoding)
  333. log.Printf("%s, %s: about to register this spectator", gid.Id, s.Id)
  334. game.sregister <- s
  335. log.Printf("%s, %s: registered spectator", gid.Id, s.Id)
  336. defer func() {
  337. log.Printf("%s, %s: about to unregister this spectator", gid.Id, s.Id)
  338. game.sunregister <- s
  339. log.Printf("%s, %s: unregistered spectator", gid.Id, s.Id)
  340. }()
  341. go s.Sender()
  342. log.Printf("%s -> %s: s.sender went", gid.Id, s.Id)
  343. s.Recv()
  344. log.Printf("game %s: spectator %+v has been disconnected from this game", gid.Id, s)
  345. }
  346. log.Printf("exiting AddPlayer")
  347. }