package main import ( "log" "sort" "sync" "time" ) const maxPlayer = 128 type BotHealth struct { RobotId string `json:"robot_id"` Health int `json:"health"` } type Scanner struct { Id string `json:"id"` Type string `json:"type"` } type MapLock struct { m map[string]*game sync.RWMutex } // get is a function that returns a game if found, and creates one if // not found and force is true. In order to get a hash (rather than use // the string you pass) send "" for id. func (ml *MapLock) get(id string) *game { ml.Lock() g, _ := ml.m[id] ml.Unlock() return g } func (ml *MapLock) add(g *game) { ml.Lock() ml.m[g.id] = g ml.Unlock() } type WinnerMap struct { m map[string]int sync.RWMutex } type game struct { id string players map[*player]bool projectiles map[*Projectile]bool splosions map[*Splosion]bool obstacles []Obstacle register chan *player unregister chan *player turn int players_remaining int width, height float32 spectators map[*Spectator]bool sregister chan *Spectator sunregister chan *Spectator kill chan bool repair_hp int repair_rate float32 tick_duration int winners WinnerMap } func NewGame(id string, width, height float32, tick int) *game { g := &game{ id: id, register: make(chan *player), unregister: make(chan *player, maxPlayer), projectiles: make(map[*Projectile]bool), splosions: make(map[*Splosion]bool), obstacles: GenerateObstacles(conf.Obstacles, width, height), players: make(map[*player]bool), turn: 0, width: width, height: height, spectators: make(map[*Spectator]bool), sregister: make(chan *Spectator), sunregister: make(chan *Spectator), kill: make(chan bool, maxPlayer), repair_hp: 5, repair_rate: 3.0, tick_duration: tick, players_remaining: 2, winners: WinnerMap{m: make(map[string]int)}, } return g } func (g *game) tick(payload *Boardstate) { g.players_remaining = 0 payload.Obstacles = g.obstacles // Update Players for p := range g.players { living_robots := 0 for _, r := range p.Robots { if r.Health > 0 { living_robots++ r.Tick(g) } if len(r.Message) > 0 { if len(r.Message) > 100 { r.Message = r.Message[0:99] } payload.Messages = append(payload.Messages, r.Message) } payload.OtherRobots = append( payload.OtherRobots, r.GetTruncatedDetails()) payload.AllBots = append( payload.AllBots, BotHealth{RobotId: r.Id, Health: r.Health}) } if living_robots > 0 { g.players_remaining++ } } // Update Projectiles for pr := range g.projectiles { pr.Tick(g) } // We do this here, because the tick calls can alter g.projectiles for pr := range g.projectiles { payload.Projectiles = append(payload.Projectiles, *pr) } // Update Splosions for s := range g.splosions { s.Tick() if !s.Alive() { delete(g.splosions, s) } payload.Splosions = append(payload.Splosions, *s) } } func (g *game) sendUpdate(payload *Boardstate) { // Ensure that the robots are always sent in a consistent order sort.Sort(RobotSorter{Robots: payload.OtherRobots}) sort.Sort(AllRobotSorter{Robots: payload.AllBots}) for p := range g.players { // Copy the payload but only add the robots in scanner range player_payload := NewBoardstate() player_payload.Messages = payload.Messages player_payload.Splosions = payload.Splosions player_payload.Obstacles = payload.Obstacles player_payload.AllBots = payload.AllBots player_payload.Turn = payload.Turn for _, r := range p.Robots { player_payload.MyRobots = append(player_payload.MyRobots, *r) player_payload.OtherRobots = append( player_payload.OtherRobots, r.GetTruncatedDetails()) } player_payload.Projectiles = []Projectile{} player_payload.Obstacles = []Obstacle{} living_robots := 0 for _, r := range p.Robots { if r.Health > 0 { living_robots++ // Filter robots by scanner for player := range g.players { for _, scan_entry := range r.Scanners { for _, r := range player.Robots { if r.Id == scan_entry.Id { player_payload.OtherRobots = append( player_payload.OtherRobots, r.GetTruncatedDetails()) } } } } // Filter projectiles for proj := range g.projectiles { if proj.Owner == r { player_payload.Projectiles = append( player_payload.Projectiles, *proj) } for _, scan_entry := range r.Scanners { if proj.Id == scan_entry.Id { player_payload.Projectiles = append( player_payload.Projectiles, *proj) } } } // Filter objects for _, ob := range g.obstacles { if ob.distance_from_point(r.Position) < float32(r.Stats.ScannerRadius)+r.ScanCounter { player_payload.Obstacles = append( player_payload.Obstacles, ob) } } } } if living_robots == 0 { player_payload.OtherRobots = payload.OtherRobots player_payload.Projectiles = payload.Projectiles player_payload.Obstacles = payload.Obstacles } p.send <- player_payload } for s := range g.spectators { s.send <- payload } } func (g *game) run() { var t0, t1 time.Time ticker := time.NewTicker(time.Duration(conf.Tick) * time.Millisecond) for { select { case <-g.kill: log.Printf("game %s: received kill signal, dying gracefully", g.id) games.Lock() for player := range g.players { close(player.send) } delete(games.m, g.id) games.Unlock() return case p := <-g.register: g.players[p] = true case p := <-g.unregister: delete(g.players, p) close(p.send) case s := <-g.sregister: g.spectators[s] = true case s := <-g.sunregister: delete(g.spectators, s) close(s.send) case <-ticker.C: t0 = time.Now() payload := NewBoardstate() g.turn++ payload.Turn = g.turn if *verbose { log.Printf("\033[2JTurn: %v", g.turn) log.Printf("Players: %v", len(g.players)) log.Printf("Projectiles: %v", len(g.projectiles)) log.Printf("Explosions: %v", len(g.splosions)) } // UPDATE GAME STATE if end, data := g.gameOver(); end { g.sendGameOver(data) } g.tick(payload) t1 = time.Now() if *verbose { log.Printf("Turn Processes %v\n", t1.Sub(t0)) } // SEND THE UPDATE TO EACH PLAYER g.sendUpdate(payload) t1 = time.Now() if *verbose { log.Printf("Sent Payload %v\n", t1.Sub(t0)) } } } } func (g *game) gameOver() (bool, *GameOver) { over := false var stats *GameOver if g.players_remaining <= 1 && len(g.players) > 1 { g.obstacles = GenerateObstacles(conf.Obstacles, g.width, g.height) log.Printf("game %s: game over", g.id) stats = NewGameOver() for p := range g.players { for _, r := range p.Robots { if r.Health > 0 { log.Printf("Robot %v Survived", r.Id) g.winners.Lock() g.winners.m[r.Id] += 1 g.winners.Unlock() stats.Winners = append(stats.Winners, r.Id) } r.reset(g) } } over = true } return over, stats } func (g *game) sendGameOver(eg *GameOver) { log.Printf("sending out game over message: %+v", eg) for p := range g.players { p.send <- eg } for s := range g.spectators { s.send <- eg } }