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.

347 lines
7.8 KiB

7 years ago
7 years ago
7 years ago
  1. package botserv
  2. // delete me
  3. import (
  4. "log"
  5. "sort"
  6. "sync"
  7. "time"
  8. "bitbucket.org/smcquay/bandwidth"
  9. )
  10. const maxPlayer = 128
  11. type BotHealth struct {
  12. RobotId string `json:"robot_id"`
  13. Health int `json:"health"`
  14. }
  15. type Scanner struct {
  16. Id string `json:"id"`
  17. Type string `json:"type"`
  18. }
  19. type BotStats struct {
  20. Kills int
  21. Deaths int
  22. Suicides int
  23. Shots int
  24. DirectHits int
  25. Hits int
  26. Wins int
  27. }
  28. type PlayerStats struct {
  29. BotStats map[string]*BotStats
  30. Wins int
  31. }
  32. type GameStats struct {
  33. PlayerStats map[string]*PlayerStats
  34. sync.RWMutex
  35. }
  36. type Game struct {
  37. id string
  38. players map[*player]bool
  39. projectiles map[*Projectile]bool
  40. splosions map[*Splosion]bool
  41. obstacles []Obstacle
  42. obstacle_count int
  43. register chan *player
  44. unregister chan *player
  45. turn int
  46. players_remaining int
  47. width, height float32
  48. maxPoints int
  49. spectators map[*Spectator]bool
  50. sregister chan *Spectator
  51. sunregister chan *Spectator
  52. kill chan bool
  53. repair_hp int
  54. repair_rate float32
  55. tick_duration int
  56. stats GameStats
  57. mode GameMode
  58. bw *bandwidth.Bandwidth
  59. }
  60. type GameMode interface {
  61. setup(g *Game)
  62. tick(gg *Game, payload *Boardstate)
  63. gameOver(gg *Game) (bool, *GameOver)
  64. }
  65. func NewGame(id string, width, height float32, obstacles, tick, maxPoints int, mode string) (*Game, error) {
  66. bw, err := bandwidth.NewBandwidth(
  67. []int{1, 10, 60},
  68. 1*time.Second,
  69. )
  70. if err != nil {
  71. return nil, err
  72. }
  73. go bw.Run()
  74. g := &Game{
  75. id: id,
  76. register: make(chan *player, maxPlayer),
  77. unregister: make(chan *player, maxPlayer),
  78. projectiles: make(map[*Projectile]bool),
  79. splosions: make(map[*Splosion]bool),
  80. obstacles: GenerateObstacles(obstacles, width, height),
  81. obstacle_count: obstacles,
  82. players: make(map[*player]bool),
  83. turn: 0,
  84. width: width,
  85. height: height,
  86. maxPoints: maxPoints,
  87. spectators: make(map[*Spectator]bool),
  88. sregister: make(chan *Spectator),
  89. sunregister: make(chan *Spectator),
  90. kill: make(chan bool),
  91. repair_hp: 5,
  92. repair_rate: 3.0,
  93. tick_duration: tick,
  94. players_remaining: 2,
  95. stats: GameStats{PlayerStats: make(map[string]*PlayerStats)},
  96. bw: bw,
  97. }
  98. if mode == "melee" {
  99. g.mode = &melee{respawn: make(map[*Robot]float64)}
  100. } else {
  101. g.mode = &deathmatch{}
  102. }
  103. g.mode.setup(g)
  104. return g, nil
  105. }
  106. func (g *Game) tick(payload *Boardstate) {
  107. g.players_remaining = 0
  108. payload.Objects = MinifyObstacles(g.obstacles)
  109. // Update Players
  110. for p := range g.players {
  111. living_robots := 0
  112. for _, r := range p.Robots {
  113. if r.Health > 0 {
  114. living_robots++
  115. r.Tick(g)
  116. }
  117. if len(r.Message) > 0 {
  118. if len(r.Message) > 100 {
  119. r.Message = r.Message[0:99]
  120. }
  121. payload.Messages = append(payload.Messages, r.Message)
  122. }
  123. payload.OtherRobots = append(
  124. payload.OtherRobots,
  125. r.GetTruncatedDetails())
  126. payload.AllBots = append(
  127. payload.AllBots,
  128. BotHealth{RobotId: r.Id, Health: r.Health})
  129. }
  130. if living_robots > 0 {
  131. g.players_remaining++
  132. }
  133. }
  134. // Update Projectiles
  135. for pr := range g.projectiles {
  136. pr.Tick(g)
  137. }
  138. // We do this here, because the tick calls can alter g.projectiles
  139. for pr := range g.projectiles {
  140. payload.Projectiles = append(payload.Projectiles, *pr)
  141. }
  142. // Update Splosions
  143. for s := range g.splosions {
  144. s.Tick()
  145. if !s.Alive() {
  146. delete(g.splosions, s)
  147. }
  148. payload.Splosions = append(payload.Splosions, *s)
  149. }
  150. }
  151. func (g *Game) sendUpdate(payload *Boardstate) {
  152. // Ensure that the robots are always sent in a consistent order
  153. sort.Sort(RobotSorter{Robots: payload.OtherRobots})
  154. sort.Sort(AllRobotSorter{Robots: payload.AllBots})
  155. for p := range g.players {
  156. // Copy the payload but only add the robots in scanner range
  157. player_payload := NewBoardstate()
  158. player_payload.Messages = payload.Messages
  159. player_payload.AllBots = payload.AllBots
  160. player_payload.Turn = payload.Turn
  161. for _, r := range p.Robots {
  162. player_payload.MyRobots = append(player_payload.MyRobots, *r)
  163. // player_payload.OtherRobots = append(
  164. // player_payload.OtherRobots,
  165. // r.GetTruncatedDetails())
  166. }
  167. player_payload.Objects = [][4]int{}
  168. player_payload.Splosions = []Splosion{}
  169. player_payload.Projectiles = []Projectile{}
  170. living_robots := 0
  171. for _, r := range p.Robots {
  172. if r.Health > 0 {
  173. living_robots++
  174. // Filter robots by scanner
  175. for player := range g.players {
  176. for _, scan_entry := range r.Scanners {
  177. for _, r := range player.Robots {
  178. if r.Id == scan_entry.Id {
  179. player_payload.OtherRobots = append(
  180. player_payload.OtherRobots,
  181. r.GetTruncatedDetails())
  182. }
  183. }
  184. }
  185. }
  186. // Filter projectiles
  187. for proj := range g.projectiles {
  188. if proj.Owner == r {
  189. player_payload.Projectiles = append(
  190. player_payload.Projectiles,
  191. *proj)
  192. }
  193. for _, scan_entry := range r.Scanners {
  194. if proj.Id == scan_entry.Id {
  195. player_payload.Projectiles = append(
  196. player_payload.Projectiles,
  197. *proj)
  198. }
  199. }
  200. }
  201. // Filter splosions
  202. for splo := range g.splosions {
  203. for _, scan_entry := range r.Scanners {
  204. if splo.Id == scan_entry.Id {
  205. player_payload.Splosions = append(
  206. player_payload.Splosions,
  207. *splo)
  208. }
  209. }
  210. }
  211. // Filter objects
  212. for _, ob := range g.obstacles {
  213. if ob.distance_from_point(r.Position) < float32(r.Stats.ScannerRadius)+r.ScanCounter {
  214. player_payload.Objects = append(
  215. player_payload.Objects, ob.minify())
  216. }
  217. }
  218. }
  219. }
  220. // if living_robots == 0 {
  221. // player_payload.OtherRobots = payload.OtherRobots
  222. // player_payload.Projectiles = payload.Projectiles
  223. // player_payload.Splosions = payload.Splosions
  224. // player_payload.Objects = payload.Objects
  225. // }
  226. p.send <- player_payload
  227. }
  228. for s := range g.spectators {
  229. s.send <- payload
  230. }
  231. }
  232. func (g *Game) run() {
  233. ticker := time.NewTicker(time.Duration(g.tick_duration) * time.Millisecond)
  234. for {
  235. select {
  236. case <-g.kill:
  237. log.Printf("game %s: received kill signal, dying gracefully", g.id)
  238. g.bw.Quit <- true
  239. for player := range g.players {
  240. close(player.send)
  241. }
  242. return
  243. case p := <-g.register:
  244. log.Println("registering player:", p.Id)
  245. g.players[p] = true
  246. g.stats.PlayerStats[p.Id] = &PlayerStats{
  247. BotStats: make(map[string]*BotStats),
  248. }
  249. for _, r := range p.Robots {
  250. g.stats.PlayerStats[p.Id].BotStats[r.Name] = &BotStats{}
  251. r.gameStats = g.stats.PlayerStats[p.Id].BotStats[r.Name]
  252. }
  253. case p := <-g.unregister:
  254. log.Println("unregistering player:", p.Id)
  255. delete(g.players, p)
  256. close(p.send)
  257. case s := <-g.sregister:
  258. log.Println("registering spectator:", s.Id)
  259. g.spectators[s] = true
  260. case s := <-g.sunregister:
  261. log.Println("unregistering spectator:", s.Id)
  262. delete(g.spectators, s)
  263. close(s.send)
  264. case <-ticker.C:
  265. payload := NewBoardstate()
  266. g.turn++
  267. payload.Turn = g.turn
  268. // UPDATE GAME STATE
  269. if end, data := g.mode.gameOver(g); end {
  270. g.sendGameOver(data)
  271. }
  272. g.tick(payload)
  273. g.mode.tick(g, payload)
  274. // SEND THE UPDATE TO EACH PLAYER
  275. g.sendUpdate(payload)
  276. }
  277. }
  278. log.Println("run done")
  279. }
  280. func (g *Game) sendGameOver(eg *GameOver) {
  281. log.Printf("sending out game over message: %+v", eg)
  282. for p := range g.players {
  283. p.send <- eg
  284. }
  285. for s := range g.spectators {
  286. s.send <- eg
  287. }
  288. }
  289. // returns a GameParam object popuplated by info from the game. This is
  290. // used during client/server initial negociation.
  291. func (g *Game) gameParam() *GameParam {
  292. return &GameParam{
  293. BoardSize: BoardSize{
  294. Width: g.width,
  295. Height: g.height,
  296. },
  297. MaxPoints: g.maxPoints,
  298. Type: "gameparam",
  299. }
  300. }