package main import ( v "bitbucket.org/hackerbots/vector" // "encoding/json" "errors" "log" "sort" "sync" "time" ) const maxPlayer = 128 type Boardstate struct { Robots []Robot `json:"robots"` Projectiles []Projectile `json:"projectiles"` Splosions []Splosion `json:"splosions"` Reset bool `json:"reset"` Type string `json:"type"` Turn int `json:"turn"` } func NewBoardstate() *Boardstate { return &Boardstate{ Robots: []Robot{}, Projectiles: []Projectile{}, Type: "boardstate", } } type Scanner struct { Id string `json:"id"` Position v.Point2d `json:"position"` Stats Stats `json:"stats"` } 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, force bool) (*game, error) { ml.Lock() g, ok := games.m[id] ml.Unlock() if ok { return g, nil } if !force { return nil, errors.New("game not found") } if id == "" { id = idg.Hash() } _g := NewGame(id, float32(*width), float32(*height)) go _g.run() ml.Lock() ml.m[id] = _g ml.Unlock() return _g, nil } type game struct { id string players map[*player]bool projectiles map[*Projectile]bool splosions map[*Splosion]bool register chan *player unregister chan *player turn int width, height float32 spectators map[*Spectator]bool sregister chan *Spectator sunregister chan *Spectator kill chan bool } func NewGame(id string, width, height float32) *game { g := &game{ id: id, register: make(chan *player), unregister: make(chan *player, maxPlayer), projectiles: make(map[*Projectile]bool), splosions: make(map[*Splosion]bool), 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), } return g } func (g *game) run() { var t0, t1 time.Time ticker := time.NewTicker(time.Duration(*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: payload := NewBoardstate() g.turn++ payload.Turn = g.turn t0 = time.Now() 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)) } robots_remaining := 0 // UPDATE GAME STATE for p := range g.players { if p.Robot.Health > 0 { robots_remaining++ // TODO: measure if this would be better to go and wait ... p.scan(g.players) p.nudge(g) if p.Robot.FireAt != nil { proj := p.fire(g.projectiles, g.turn) if proj != nil { g.projectiles[proj] = true } } } payload.Robots = append(payload.Robots, p.Robot) } sort.Sort(RobotSorter{Robots: payload.Robots}) payload.Projectiles = append(payload.Projectiles, g.nudgeProjectiles()..., ) for s := range g.splosions { s.Tick() if !s.Alive() { delete(g.splosions, s) } payload.Splosions = append(payload.Splosions, *s) } if robots_remaining <= 1 && len(g.players) > 1 { for p := range g.players { if p.Robot.Health > 0 { log.Printf("Robot %v Wins", p.Robot.Id) log.Printf("game %s: game over", g.id) } p.reset() } payload.Reset = true } else { payload.Reset = false } t1 = time.Now() if *verbose { log.Printf("Turn Processes %v\n", t1.Sub(t0)) } // SEND THE UPDATE TO EACH PLAYER for p := range g.players { // Copy the payload but only add the robots in scanner range player_payload := NewBoardstate() player_payload.Projectiles = payload.Projectiles player_payload.Splosions = payload.Splosions player_payload.Turn = payload.Turn player_payload.Reset = payload.Reset player_payload.Robots = append(player_payload.Robots, p.Robot) if p.Robot.Health > 0 { for player := range g.players { for _, scan_entry := range p.Robot.Scanners { if player.Robot.Id == scan_entry.Id { player_payload.Robots = append(player_payload.Robots, player.Robot) } } } } else { player_payload.Robots = payload.Robots } // x, _ := json.Marshal(player_payload) // log.Printf("%v", string(x)) p.send <- player_payload } for s := range g.spectators { s.send <- payload } t1 = time.Now() if *verbose { log.Printf("Sent Payload %v\n", t1.Sub(t0)) } } } } func (g *game) nudgeProjectiles() (rprojectiles []Projectile) { rprojectiles = make([]Projectile, 0) for p := range g.projectiles { newPos := move(p.Position, p.MoveTo, float32(p.Speed), delta) hit_player := false for player := range g.players { if player.Robot.Id == p.Id { continue } dist := v.Distance(player.Robot.Position, p.Position) if dist < 5.0 { hit_player = true } } // Are we going to intersect the destination zone in this tick? r_dest := v.Rect2d{ A: v.Point2d{X: p.MoveTo.X - 3, Y: p.MoveTo.Y - 3}, B: v.Point2d{X: p.MoveTo.X + 3, Y: p.MoveTo.Y + 3}} travel := newPos.Sub(p.Position) arrived, _, _ := v.RectIntersection(r_dest, p.Position, travel) if arrived || hit_player { delete(g.projectiles, p) // Spawn a splosion splo := &Splosion{ Id: p.Id, Position: p.Position, Radius: p.Radius, MaxDamage: 10, MinDamage: 5, Lifespan: 8, } g.splosions[splo] = true for player := range g.players { dist := v.Distance(player.Robot.Position, p.Position) if dist < float32(p.Radius) { // TODO map damage Max to Min based on distance from explosion if player.Robot.Health > 0 { player.Robot.Health -= p.Damage if player.Robot.Health <= 0 { } } } } } else { p.Position.X = newPos.X p.Position.Y = newPos.Y rprojectiles = append(rprojectiles, *p) } } return }