From e39a97089ee9bc8ef0b206578614a982a284e5a7 Mon Sep 17 00:00:00 2001 From: Fraser Graham Date: Fri, 8 Nov 2013 21:25:42 -0800 Subject: [PATCH 1/2] First pass of multibot support --- control.go | 11 +- game.go | 134 ++++++++++-------- player.go | 373 ++++++++++++++++++++++++++------------------------ projectile.go | 40 +++--- protocol.go | 55 ++++++-- robot.go | 15 +- 6 files changed, 350 insertions(+), 278 deletions(-) diff --git a/control.go b/control.go index e0123b3..168ea8a 100644 --- a/control.go +++ b/control.go @@ -83,11 +83,14 @@ func listGames(w http.ResponseWriter, req *http.Request) { ids := make([]gl, 0) for id, g := range games.m { players := make([]pout, 0) + // TODO - players instead of robots? for p, _ := range g.players { - players = append(players, pout{ - Name: p.Robot.Name, - Id: p.Robot.Id, - }) + for _, r := range p.Robots { + players = append(players, pout{ + Name: r.Name, + Id: r.Id, + }) + } } ids = append(ids, gl{ Id: id, diff --git a/game.go b/game.go index 04af1ff..a4be255 100644 --- a/game.go +++ b/game.go @@ -106,30 +106,40 @@ func NewGame(id string, width, height float32) *game { func (g *game) tick(payload *Boardstate) int { robots_remaining := 0 + players_remaining := 0 payload.Obstacles = g.obstacles // Update Players for p := range g.players { - if p.Robot.Health > 0 { - robots_remaining++ - p.Tick(g) - } + living_robots := 0 - if len(p.Robot.Message) > 0 { - if len(p.Robot.Message) > 100 { - p.Robot.Message = p.Robot.Message[0:99] + for _, r := range p.Robots { + if r.Health > 0 { + living_robots++ + p.Tick(g) } - payload.Messages = append(payload.Messages, p.Robot.Message) + + 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}) } - payload.OtherRobots = append( - payload.OtherRobots, - p.Robot.GetTruncatedDetails()) - - payload.AllBots = append( - payload.AllBots, - BotHealth{RobotId: p.Robot.Id, Health: p.Robot.Health}) + if living_robots > 0 { + players_remaining++ + robots_remaining += living_robots + } } // Update Projectiles @@ -151,7 +161,7 @@ func (g *game) tick(payload *Boardstate) int { payload.Splosions = append(payload.Splosions, *s) } - return robots_remaining + return players_remaining } func (g *game) send_update(payload *Boardstate) { @@ -160,6 +170,7 @@ func (g *game) send_update(payload *Boardstate) { 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 @@ -168,53 +179,63 @@ func (g *game) send_update(payload *Boardstate) { player_payload.AllBots = payload.AllBots player_payload.Turn = payload.Turn player_payload.Reset = payload.Reset - player_payload.MyRobots = append(player_payload.MyRobots, p.Robot) - player_payload.OtherRobots = append( - player_payload.OtherRobots, - p.Robot.GetTruncatedDetails()) + 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 - if p.Robot.Health > 0 { - // Filter robots by scanner - for player := range g.players { - for _, scan_entry := range p.Robot.Scanners { - if player.Robot.Id == scan_entry.Id { - player_payload.OtherRobots = append( - player_payload.OtherRobots, - player.Robot.GetTruncatedDetails()) + 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 { + // Filter projectiles + for proj := range g.projectiles { - if proj.Owner == p { - player_payload.Projectiles = append( - player_payload.Projectiles, - *proj) - } - - for _, scan_entry := range p.Robot.Scanners { - if proj.Id == scan_entry.Id { + if proj.Owner == &r { player_payload.Projectiles = append( player_payload.Projectiles, *proj) } - } - } - // Filter objects - for _, ob := range g.obstacles { - if ob.distance_from_point(p.Robot.Position) < float32(p.Robot.Stats.ScannerRadius)+p.Robot.ScanCounter { - player_payload.Obstacles = append( - player_payload.Obstacles, - ob) + 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) + } } } - } else { + } + + if living_robots == 0 { player_payload.OtherRobots = payload.OtherRobots player_payload.Projectiles = payload.Projectiles player_payload.Obstacles = payload.Obstacles @@ -266,17 +287,20 @@ func (g *game) run() { } // UPDATE GAME STATE - robots_remaining := g.tick(payload) + players_remaining := g.tick(payload) // Determine end game? - if robots_remaining <= 1 && len(g.players) > 1 { + if players_remaining <= 1 && len(g.players) > 1 { + g.obstacles = GenerateObstacles(5, g.width, g.height) + log.Printf("game %s: game over", g.id) + 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) + for _, r := range p.Robots { + if r.Health > 0 { + log.Printf("Robot %v Survived", r.Id) + } + r.reset(g) } - p.reset(g) - g.obstacles = GenerateObstacles(5, g.width, g.height) } payload.Reset = true } else { diff --git a/player.go b/player.go index d1f2783..1acebdf 100644 --- a/player.go +++ b/player.go @@ -10,7 +10,7 @@ import ( type player struct { ws *websocket.Conn - Robot Robot + Robots []Robot send chan *Boardstate Instruction Instruction } @@ -23,15 +23,16 @@ func (p *player) sender() { } } p.ws.Close() - log.Printf("player %s: sender close", p.Robot.Id) + // TODO + log.Printf("player %s: sender close", p.Robots[0].Id) } func (p *player) recv() { for { // XXX: need to mark myself as having received something, also binding // such action to a particular game turn ID - var msg Instruction - err := websocket.JSON.Receive(p.ws, &msg) + var msgs PlayerInstructions + err := websocket.JSON.Receive(p.ws, &msgs) if err != nil { // TODO: perhaps we could be a bit more precise in the handling of // this 'error' by selecting on some kill signal channel and this @@ -40,79 +41,89 @@ func (p *player) recv() { break } - if msg.Repair != nil && *msg.Repair == true { - p.Robot.TargetSpeed = 0 - p.Robot.FireAt = nil - p.Robot.MoveTo = nil - if p.Robot.RepairCounter <= 0 { - p.Robot.RepairCounter = 3.0 - } - } else if msg.Scan != nil && *msg.Scan == true { - p.Robot.TargetSpeed = 0 - p.Robot.FireAt = nil - p.Robot.MoveTo = nil - p.Robot.ActiveScan = true - } else { - p.Robot.RepairCounter = 0 - p.Robot.ActiveScan = false + for _, r := range p.Robots { - // Reapiring halts all other activity - if msg.MoveTo != nil { - p.Robot.MoveTo = msg.MoveTo - } - if msg.FireAt != nil { - p.Robot.FireAt = msg.FireAt + msg, ok := msgs.RobotInstructions[r.Id] + if !ok { + continue } - if msg.TargetSpeed != nil { - p.Robot.TargetSpeed = float32(*msg.TargetSpeed) + if msg.Repair != nil && *msg.Repair == true { + r.TargetSpeed = 0 + r.FireAt = nil + r.MoveTo = nil + if r.RepairCounter <= 0 { + r.RepairCounter = 3.0 + } + } else if msg.Scan != nil && *msg.Scan == true { + r.TargetSpeed = 0 + r.FireAt = nil + r.MoveTo = nil + r.ActiveScan = true } else { - p.Robot.TargetSpeed = p.Robot.Stats.Speed + r.RepairCounter = 0 + r.ActiveScan = false + + // Reapiring halts all other activity + if msg.MoveTo != nil { + r.MoveTo = msg.MoveTo + } + if msg.FireAt != nil { + r.FireAt = msg.FireAt + } + + if msg.TargetSpeed != nil { + r.TargetSpeed = float32(*msg.TargetSpeed) + } else { + r.TargetSpeed = r.Stats.Speed + } + + if msg.Probe != nil { + r.Probe = msg.Probe + r.ProbeResult = nil + } else { + r.Probe = nil + } } - if msg.Probe != nil { - p.Robot.Probe = msg.Probe - p.Robot.ProbeResult = nil - } else { - p.Robot.Probe = nil + if msg.Message != nil { + r.Message = *msg.Message } } - - if msg.Message != nil { - p.Robot.Message = *msg.Message - } - } - log.Printf("player %s: recv close", p.Robot.Id) + + log.Printf("player %s: recv close", p.Robots[0].Id) p.ws.Close() } -func (p *player) checkCollisions(g *game, move_vector v.Vector2d) (bool, v.Point2d, *player) { +func (r *Robot) checkCollisions(g *game, move_vector v.Vector2d) (bool, v.Point2d, *Robot) { collision := false intersection_point := v.Point2d{X: 0, Y: 0} // Check Walls r_walls := v.Rect2d{A: v.Point2d{X: 0, Y: 0}, B: v.Point2d{X: g.width, Y: g.height}} - collision, _, pos := v.RectIntersection(r_walls, p.Robot.Position, move_vector) + collision, _, pos := v.RectIntersection(r_walls, r.Position, move_vector) if collision { return collision, pos, nil } // Check Other Bots for player := range g.players { - if player.Robot.Id == p.Robot.Id { - continue - } - player_rect := v.RectFromPoint(player.Robot.Position, 3) - collision, _, pos := v.RectIntersection(player_rect, p.Robot.Position, move_vector) - if collision { - return collision, pos, player + for _, bot := range player.Robots { + if bot.Id == r.Id { + continue + } + player_rect := v.RectFromPoint(bot.Position, 3) + collision, _, pos := v.RectIntersection(player_rect, r.Position, move_vector) + if collision { + return collision, pos, &bot + } } } // Check Obstacles for _, obj := range g.obstacles { - collision, _, pos := v.RectIntersection(obj.Bounds, p.Robot.Position, move_vector) + collision, _, pos := v.RectIntersection(obj.Bounds, r.Position, move_vector) if collision { return collision, pos, nil } @@ -122,184 +133,188 @@ func (p *player) checkCollisions(g *game, move_vector v.Vector2d) (bool, v.Point } func (p *player) Tick(g *game) { - p.Robot.Collision = false - p.Robot.Hit = false - p.scan(g) + for _, r := range p.Robots { - // Adjust Speed - if p.Robot.Speed < p.Robot.TargetSpeed { - p.Robot.Speed += (p.Robot.Stats.Acceleration * delta) - if p.Robot.Speed > p.Robot.TargetSpeed { - p.Robot.Speed = p.Robot.TargetSpeed - } + r.Collision = false + r.Hit = false + r.scan(g) - } else if float32(math.Abs(float64(p.Robot.Speed-p.Robot.TargetSpeed))) > v.Epsilon { - p.Robot.Speed -= (p.Robot.Stats.Acceleration * delta) - } else { - p.Robot.Speed = p.Robot.TargetSpeed - } - - // Adjust Heading - current_heading := p.Robot.Heading - if current_heading.Mag() == 0 && p.Robot.MoveTo != nil { - // We may have been stopped before this and had no heading - current_heading = p.Robot.MoveTo.Sub(p.Robot.Position).Normalize() - } - - new_heading := current_heading - if p.Robot.MoveTo != nil { - // Where do we WANT to be heading? - new_heading = p.Robot.MoveTo.Sub(p.Robot.Position).Normalize() - } - - if new_heading.Mag() > 0 { - // Is our direction change too much? Hard coding to 5 degrees/s for now - angle := v.Angle(current_heading, new_heading) * v.Rad2deg - - dir := 1.0 - if angle < 0 { - dir = -1.0 - } - - // Max turn radius in this case is in degrees per second - if float32(math.Abs(float64(angle))) > (float32(p.Robot.Stats.TurnSpeed) * delta) { - // New heading should be a little less, take current heading and - // rotate by the max turn radius per frame. - rot := (float32(p.Robot.Stats.TurnSpeed) * delta) * v.Deg2rad - - new_heading = current_heading.Rotate(rot * float32(dir)) - } - - move_vector := new_heading.Scale(p.Robot.Speed * delta) - collision, intersection_point, hit_player := p.checkCollisions(g, move_vector) - if collision { - p.Robot.Collision = true - if hit_player != nil { - hit_player.Robot.Health -= int(p.Robot.Speed / 10.0) - hit_player.Robot.Speed = (hit_player.Robot.Speed * 0.1) - hit_player.Robot.Heading = p.Robot.Heading + // Adjust Speed + if r.Speed < r.TargetSpeed { + r.Speed += (r.Stats.Acceleration * delta) + if r.Speed > r.TargetSpeed { + r.Speed = r.TargetSpeed } - move_by := intersection_point.Sub(p.Robot.Position) - move_dist := move_by.Scale(float32(math.Floor(float64(move_by.Mag()-3.0))) / move_by.Mag()) - p.Robot.Position = p.Robot.Position.Add(move_dist) - p.Robot.Health -= int(p.Robot.Speed / 10.0) - p.Robot.MoveTo = &p.Robot.Position - p.Robot.Speed = (p.Robot.Speed * 0.1) - p.Robot.Heading = p.Robot.Heading.Scale(-1.0) + } else if float32(math.Abs(float64(r.Speed-r.TargetSpeed))) > v.Epsilon { + r.Speed -= (r.Stats.Acceleration * delta) } else { - p.Robot.Position = p.Robot.Position.Add(move_vector) - if new_heading.Mag() > 0 { - p.Robot.Heading = new_heading + r.Speed = r.TargetSpeed + } + + // Adjust Heading + current_heading := r.Heading + if current_heading.Mag() == 0 && r.MoveTo != nil { + // We may have been stopped before this and had no heading + current_heading = r.MoveTo.Sub(r.Position).Normalize() + } + + new_heading := current_heading + if r.MoveTo != nil { + // Where do we WANT to be heading? + new_heading = r.MoveTo.Sub(r.Position).Normalize() + } + + if new_heading.Mag() > 0 { + // Is our direction change too much? Hard coding to 5 degrees/s for now + angle := v.Angle(current_heading, new_heading) * v.Rad2deg + + dir := 1.0 + if angle < 0 { + dir = -1.0 + } + + // Max turn radius in this case is in degrees per second + if float32(math.Abs(float64(angle))) > (float32(r.Stats.TurnSpeed) * delta) { + // New heading should be a little less, take current heading and + // rotate by the max turn radius per frame. + rot := (float32(r.Stats.TurnSpeed) * delta) * v.Deg2rad + + new_heading = current_heading.Rotate(rot * float32(dir)) + } + + move_vector := new_heading.Scale(r.Speed * delta) + collision, intersection_point, hit_robot := r.checkCollisions(g, move_vector) + if collision { + r.Collision = true + if hit_robot != nil { + hit_robot.Health -= int(r.Speed / 10.0) + hit_robot.Speed = (hit_robot.Speed * 0.1) + hit_robot.Heading = r.Heading + } + move_by := intersection_point.Sub(r.Position) + move_dist := move_by.Scale(float32(math.Floor(float64(move_by.Mag()-3.0))) / move_by.Mag()) + + r.Position = r.Position.Add(move_dist) + r.Health -= int(r.Speed / 10.0) + r.MoveTo = &r.Position + r.Speed = (r.Speed * 0.1) + r.Heading = r.Heading.Scale(-1.0) } else { - log.Printf("Zero Heading %v", new_heading) + r.Position = r.Position.Add(move_vector) + if new_heading.Mag() > 0 { + r.Heading = new_heading + } else { + log.Printf("Zero Heading %v", new_heading) + } + } + } + + // We only self repair when we're stopped + if math.Abs(float64(r.Speed)) < v.Epsilon && r.RepairCounter > 0 { + r.RepairCounter -= delta + if r.RepairCounter < 0 { + r.Health += g.repair_hp + r.RepairCounter = g.repair_rate + } + } + + // We only self repair when we're stopped + if math.Abs(float64(r.Speed)) < v.Epsilon && r.ActiveScan { + r.ScanCounter += delta * float32(r.Stats.ScannerRadius) * 0.1 + } else if r.ScanCounter > 0 { + r.ScanCounter -= delta * float32(r.Stats.ScannerRadius) * 0.05 + if r.ScanCounter <= 0 { + r.ScanCounter = 0 + } + } + + if r.FireAt != nil { + proj := r.fire(g.projectiles, g.turn) + if proj != nil { + g.projectiles[proj] = true + } + } + + if r.Probe != nil && r.ProbeResult == nil { + probe_vector := r.Probe.Sub(r.Position) + coll, pos, _ := r.checkCollisions(g, probe_vector) + if coll { + r.ProbeResult = &pos } } } - - // We only self repair when we're stopped - if math.Abs(float64(p.Robot.Speed)) < v.Epsilon && p.Robot.RepairCounter > 0 { - p.Robot.RepairCounter -= delta - if p.Robot.RepairCounter < 0 { - p.Robot.Health += g.repair_hp - p.Robot.RepairCounter = g.repair_rate - } - } - - // We only self repair when we're stopped - if math.Abs(float64(p.Robot.Speed)) < v.Epsilon && p.Robot.ActiveScan { - p.Robot.ScanCounter += delta * float32(p.Robot.Stats.ScannerRadius) * 0.1 - } else if p.Robot.ScanCounter > 0 { - p.Robot.ScanCounter -= delta * float32(p.Robot.Stats.ScannerRadius) * 0.05 - if p.Robot.ScanCounter <= 0 { - p.Robot.ScanCounter = 0 - } - } - - if p.Robot.FireAt != nil { - proj := p.fire(g.projectiles, g.turn) - if proj != nil { - g.projectiles[proj] = true - } - } - - if p.Robot.Probe != nil && p.Robot.ProbeResult == nil { - probe_vector := p.Robot.Probe.Sub(p.Robot.Position) - coll, pos, _ := p.checkCollisions(g, probe_vector) - if coll { - p.Robot.ProbeResult = &pos - } - } - } -func (p *player) scan(g *game) { - p.Robot.Scanners = p.Robot.Scanners[:0] +func (r *Robot) scan(g *game) { + r.Scanners = r.Scanners[:0] for player, _ := range g.players { - if player.Robot.Id == p.Robot.Id || player.Robot.Health <= 0 { - continue - } - dist := v.Distance(player.Robot.Position, p.Robot.Position) - if dist < float32(p.Robot.Stats.ScannerRadius+int(p.Robot.ScanCounter)) { - s := Scanner{ - Id: player.Robot.Id, - Type: "robot", + for _, bot := range player.Robots { + if bot.Id == r.Id || bot.Health <= 0 { + continue + } + dist := v.Distance(bot.Position, r.Position) + if dist < float32(r.Stats.ScannerRadius+int(r.ScanCounter)) { + s := Scanner{ + Id: bot.Id, + Type: "robot", + } + r.Scanners = append(r.Scanners, s) } - p.Robot.Scanners = append(p.Robot.Scanners, s) } } for proj, _ := range g.projectiles { - if proj.Owner == p { + if proj.Owner == r { continue } - dist := v.Distance(proj.Position, p.Robot.Position) - if dist < float32(p.Robot.Stats.ScannerRadius+int(p.Robot.ScanCounter)) { + dist := v.Distance(proj.Position, r.Position) + if dist < float32(r.Stats.ScannerRadius+int(r.ScanCounter)) { s := Scanner{ Id: proj.Id, Type: "projectile", } - p.Robot.Scanners = append(p.Robot.Scanners, s) + r.Scanners = append(r.Scanners, s) } } } -func (p *player) fire(projectiles map[*Projectile]bool, turn int) *Projectile { +func (r *Robot) fire(projectiles map[*Projectile]bool, turn int) *Projectile { // Throttle the fire rate - time_since_fired := (float32(turn) * (delta * 1000)) - (float32(p.Robot.LastFired) * (delta * 1000)) - if time_since_fired < float32(p.Robot.Stats.FireRate) { + time_since_fired := (float32(turn) * (delta * 1000)) - (float32(r.LastFired) * (delta * 1000)) + if time_since_fired < float32(r.Stats.FireRate) { return nil } - p.Robot.LastFired = turn + r.LastFired = turn return &Projectile{ Id: idg.Hash(), - Position: p.Robot.Position, - MoveTo: *p.Robot.FireAt, - Damage: p.Robot.Stats.WeaponDamage, - Radius: p.Robot.Stats.WeaponRadius, - Speed: p.Robot.Stats.WeaponSpeed, - Owner: p, + Position: r.Position, + MoveTo: *r.FireAt, + Damage: r.Stats.WeaponDamage, + Radius: r.Stats.WeaponRadius, + Speed: r.Stats.WeaponSpeed, + Owner: r, } } -func (p *player) reset(g *game) { +func (r *Robot) reset(g *game) { for { start_pos := v.Point2d{ X: rand.Float32() * float32(g.width), Y: rand.Float32() * float32(g.height), } - p.Robot.MoveTo = &start_pos - p.Robot.Position = start_pos - p.Robot.Health = p.Robot.Stats.Hp + r.MoveTo = &start_pos + r.Position = start_pos + r.Health = r.Stats.Hp // Check Obstacles retry := false for _, obj := range g.obstacles { - _, inside, _ := v.RectIntersection(obj.Bounds, p.Robot.Position, v.Vector2d{0, 0}) + _, inside, _ := v.RectIntersection(obj.Bounds, r.Position, v.Vector2d{0, 0}) if inside { retry = true } diff --git a/projectile.go b/projectile.go index 97c1913..0aa0ad6 100644 --- a/projectile.go +++ b/projectile.go @@ -12,7 +12,7 @@ type Projectile struct { Radius int `json:"-"` Speed float32 `json:"-"` Damage int `json:"-"` - Owner *player `json:"-"` + Owner *Robot `json:"-"` } func (p *Projectile) Tick(g *game) { @@ -23,21 +23,23 @@ func (p *Projectile) Tick(g *game) { hit_player := false for player := range g.players { - if player == p.Owner { - continue - } + for _, r := range player.Robots { + if &r == p.Owner { + continue + } - player_rect := v.RectFromPoint(player.Robot.Position, 3) - collision, _, _ := v.RectIntersection(player_rect, p.Position, v_scaled) - if collision { - hit_player = true + player_rect := v.RectFromPoint(r.Position, 3) + collision, _, _ := v.RectIntersection(player_rect, p.Position, v_scaled) + if collision { + hit_player = true - if player.Robot.Health > 0 { - // Direct hit causes more damage - log.Printf("Direct Hit %v, Dmg:%v", player.Robot.Id, p.Damage) + if r.Health > 0 { + // Direct hit causes more damage + log.Printf("Direct Hit %v, Dmg:%v", r.Id, p.Damage) - player.Robot.Health -= p.Damage - player.Robot.Hit = true + r.Health -= p.Damage + r.Hit = true + } } } } @@ -73,11 +75,13 @@ func (p *Projectile) Tick(g *game) { g.splosions[splo] = true for player := range g.players { - dist := v.Distance(player.Robot.Position, p.Position) - if dist < float32(p.Radius) { - if player.Robot.Health > 0 { - player.Robot.Health -= p.Damage - player.Robot.Hit = true + for _, r := range player.Robots { + dist := v.Distance(r.Position, p.Position) + if dist < float32(p.Radius) { + if r.Health > 0 { + r.Health -= p.Damage + r.Hit = true + } } } } diff --git a/protocol.go b/protocol.go index aac2ca9..d1a115d 100644 --- a/protocol.go +++ b/protocol.go @@ -40,8 +40,29 @@ func (c *ClientID) Valid() (bool, string) { } type ClientConfig struct { - ID string `json:"id"` - Stats StatsRequest `json:"stats"` + ID string `json:"id"` + Stats map[string]StatsRequest `json:"stats"` +} + +func (config ClientConfig) Valid() bool { + total := 0 + for _, s := range config.Stats { + total += (s.Speed + + s.Hp + + s.WeaponRadius + + s.ScannerRadius + + s.Acceleration + + s.TurnSpeed + + s.FireRate + + s.WeaponDamage + + s.WeaponSpeed) + } + + // allowing for 50 pts in every category + if total > 1000 { + return false + } + return true } type BoardSize struct { @@ -152,38 +173,50 @@ func addPlayer(ws *websocket.Conn) { log.Printf("%s Waiting for client to send conf ...", player_id) err = websocket.JSON.Receive(ws, &conf) log.Printf("conf received: %+v", conf) + if err != nil { log.Printf("%s %s config parse error", gid.Id, player_id) websocket.JSON.Send(ws, NewFailure("config parse error")) return } + // TODO: verify conf's type - if conf.Stats.Valid() { + if conf.Valid() { + log.Printf("Config is Valid, continuing") _ = websocket.JSON.Send(ws, NewHandshake(player_id, true)) break } else { + log.Printf("Config is INVALID, abort") _ = websocket.JSON.Send(ws, NewHandshake(player_id, false)) } } p := &player{ - Robot: Robot{ - Stats: DeriveStats(conf.Stats), + Robots: []Robot{}, + send: make(chan *Boardstate), + ws: ws, + } + + for _, stats := range conf.Stats { + r := Robot{ + Stats: DeriveStats(stats), Id: player_id, Name: clientid.Name, - Health: conf.Stats.Hp, - Scanners: make([]Scanner, 0)}, - send: make(chan *Boardstate), - ws: ws, + Health: 10, + Scanners: make([]Scanner, 0)} + r.Health = r.Stats.Hp + log.Printf("Adding Robot: %v", r) + p.Robots = append(p.Robots, r) + r.reset(game) } - p.reset(game) + game.register <- p defer func() { game.unregister <- p }() go p.sender() p.recv() - log.Printf("game %s: player %v has been disconnected from this game\n", gid.Id, p.Robot.Id) + log.Printf("game %s: player %v has been disconnected from this game\n", gid.Id, p.Robots[0].Id) case "spectator": s := &Spectator{ send: make(chan *Boardstate), diff --git a/robot.go b/robot.go index 39e1fb8..344e966 100644 --- a/robot.go +++ b/robot.go @@ -149,17 +149,6 @@ func DeriveStats(request StatsRequest) Stats { return s } -func (s StatsRequest) Valid() bool { - total := (s.Speed + s.Hp + s.WeaponRadius + - s.ScannerRadius + s.Acceleration + s.TurnSpeed + s.FireRate) - - // allowing for 50 pts in every category - if total > 500 { - return false - } - return true -} - type Instruction struct { Message *string `json:"message,omitempty"` MoveTo *v.Point2d `json:"move_to,omitempty"` @@ -170,3 +159,7 @@ type Instruction struct { Scan *bool `json:"scan,omitempty"` Stats Stats `json:"stats"` } + +type PlayerInstructions struct { + RobotInstructions map[string]Instruction `json:"instructions"` +} From 570d8eea8e644446064c395d4d90f92c34536d41 Mon Sep 17 00:00:00 2001 From: Fraser Graham Date: Fri, 8 Nov 2013 22:26:56 -0800 Subject: [PATCH 2/2] get multibots working --- game.go | 6 +- player.go | 191 +++++++++++++++++++++++++------------------------- projectile.go | 2 +- protocol.go | 6 +- robot.go | 5 -- 5 files changed, 104 insertions(+), 106 deletions(-) diff --git a/game.go b/game.go index a4be255..4c84274 100644 --- a/game.go +++ b/game.go @@ -117,7 +117,7 @@ func (g *game) tick(payload *Boardstate) int { for _, r := range p.Robots { if r.Health > 0 { living_robots++ - p.Tick(g) + r.Tick(g) } if len(r.Message) > 0 { @@ -180,7 +180,7 @@ func (g *game) send_update(payload *Boardstate) { player_payload.Turn = payload.Turn player_payload.Reset = payload.Reset for _, r := range p.Robots { - player_payload.MyRobots = append(player_payload.MyRobots, r) + player_payload.MyRobots = append(player_payload.MyRobots, *r) player_payload.OtherRobots = append( player_payload.OtherRobots, r.GetTruncatedDetails()) @@ -209,7 +209,7 @@ func (g *game) send_update(payload *Boardstate) { // Filter projectiles for proj := range g.projectiles { - if proj.Owner == &r { + if proj.Owner == r { player_payload.Projectiles = append( player_payload.Projectiles, *proj) diff --git a/player.go b/player.go index 1acebdf..8174ead 100644 --- a/player.go +++ b/player.go @@ -10,7 +10,7 @@ import ( type player struct { ws *websocket.Conn - Robots []Robot + Robots []*Robot send chan *Boardstate Instruction Instruction } @@ -31,8 +31,9 @@ func (p *player) recv() { for { // XXX: need to mark myself as having received something, also binding // such action to a particular game turn ID - var msgs PlayerInstructions + var msgs map[string]Instruction err := websocket.JSON.Receive(p.ws, &msgs) + if err != nil { // TODO: perhaps we could be a bit more precise in the handling of // this 'error' by selecting on some kill signal channel and this @@ -43,11 +44,14 @@ func (p *player) recv() { for _, r := range p.Robots { - msg, ok := msgs.RobotInstructions[r.Id] + msg, ok := msgs[r.Id] + if !ok { continue } + // log.Printf("%v", msg.FireAt) + if msg.Repair != nil && *msg.Repair == true { r.TargetSpeed = 0 r.FireAt = nil @@ -116,7 +120,7 @@ func (r *Robot) checkCollisions(g *game, move_vector v.Vector2d) (bool, v.Point2 player_rect := v.RectFromPoint(bot.Position, 3) collision, _, pos := v.RectIntersection(player_rect, r.Position, move_vector) if collision { - return collision, pos, &bot + return collision, pos, bot } } } @@ -132,116 +136,113 @@ func (r *Robot) checkCollisions(g *game, move_vector v.Vector2d) (bool, v.Point2 return collision, intersection_point, nil } -func (p *player) Tick(g *game) { - for _, r := range p.Robots { +func (r *Robot) Tick(g *game) { + r.Collision = false + r.Hit = false + r.scan(g) - r.Collision = false - r.Hit = false - r.scan(g) - - // Adjust Speed - if r.Speed < r.TargetSpeed { - r.Speed += (r.Stats.Acceleration * delta) - if r.Speed > r.TargetSpeed { - r.Speed = r.TargetSpeed - } - - } else if float32(math.Abs(float64(r.Speed-r.TargetSpeed))) > v.Epsilon { - r.Speed -= (r.Stats.Acceleration * delta) - } else { + // Adjust Speed + if r.Speed < r.TargetSpeed { + r.Speed += (r.Stats.Acceleration * delta) + if r.Speed > r.TargetSpeed { r.Speed = r.TargetSpeed } - // Adjust Heading - current_heading := r.Heading - if current_heading.Mag() == 0 && r.MoveTo != nil { - // We may have been stopped before this and had no heading - current_heading = r.MoveTo.Sub(r.Position).Normalize() + } else if float32(math.Abs(float64(r.Speed-r.TargetSpeed))) > v.Epsilon { + r.Speed -= (r.Stats.Acceleration * delta) + } else { + r.Speed = r.TargetSpeed + } + + // Adjust Heading + current_heading := r.Heading + if current_heading.Mag() == 0 && r.MoveTo != nil { + // We may have been stopped before this and had no heading + current_heading = r.MoveTo.Sub(r.Position).Normalize() + } + + new_heading := current_heading + if r.MoveTo != nil { + // Where do we WANT to be heading? + new_heading = r.MoveTo.Sub(r.Position).Normalize() + } + + if new_heading.Mag() > 0 { + // Is our direction change too much? Hard coding to 5 degrees/s for now + angle := v.Angle(current_heading, new_heading) * v.Rad2deg + + dir := 1.0 + if angle < 0 { + dir = -1.0 } - new_heading := current_heading - if r.MoveTo != nil { - // Where do we WANT to be heading? - new_heading = r.MoveTo.Sub(r.Position).Normalize() + // Max turn radius in this case is in degrees per second + if float32(math.Abs(float64(angle))) > (float32(r.Stats.TurnSpeed) * delta) { + // New heading should be a little less, take current heading and + // rotate by the max turn radius per frame. + rot := (float32(r.Stats.TurnSpeed) * delta) * v.Deg2rad + + new_heading = current_heading.Rotate(rot * float32(dir)) } - if new_heading.Mag() > 0 { - // Is our direction change too much? Hard coding to 5 degrees/s for now - angle := v.Angle(current_heading, new_heading) * v.Rad2deg - - dir := 1.0 - if angle < 0 { - dir = -1.0 + move_vector := new_heading.Scale(r.Speed * delta) + collision, intersection_point, hit_robot := r.checkCollisions(g, move_vector) + if collision { + r.Collision = true + if hit_robot != nil { + hit_robot.Health -= int(r.Speed / 10.0) + hit_robot.Speed = (hit_robot.Speed * 0.1) + hit_robot.Heading = r.Heading } + move_by := intersection_point.Sub(r.Position) + move_dist := move_by.Scale(float32(math.Floor(float64(move_by.Mag()-3.0))) / move_by.Mag()) - // Max turn radius in this case is in degrees per second - if float32(math.Abs(float64(angle))) > (float32(r.Stats.TurnSpeed) * delta) { - // New heading should be a little less, take current heading and - // rotate by the max turn radius per frame. - rot := (float32(r.Stats.TurnSpeed) * delta) * v.Deg2rad - - new_heading = current_heading.Rotate(rot * float32(dir)) - } - - move_vector := new_heading.Scale(r.Speed * delta) - collision, intersection_point, hit_robot := r.checkCollisions(g, move_vector) - if collision { - r.Collision = true - if hit_robot != nil { - hit_robot.Health -= int(r.Speed / 10.0) - hit_robot.Speed = (hit_robot.Speed * 0.1) - hit_robot.Heading = r.Heading - } - move_by := intersection_point.Sub(r.Position) - move_dist := move_by.Scale(float32(math.Floor(float64(move_by.Mag()-3.0))) / move_by.Mag()) - - r.Position = r.Position.Add(move_dist) - r.Health -= int(r.Speed / 10.0) - r.MoveTo = &r.Position - r.Speed = (r.Speed * 0.1) - r.Heading = r.Heading.Scale(-1.0) + r.Position = r.Position.Add(move_dist) + r.Health -= int(r.Speed / 10.0) + r.MoveTo = &r.Position + r.Speed = (r.Speed * 0.1) + r.Heading = r.Heading.Scale(-1.0) + } else { + r.Position = r.Position.Add(move_vector) + if new_heading.Mag() > 0 { + r.Heading = new_heading } else { - r.Position = r.Position.Add(move_vector) - if new_heading.Mag() > 0 { - r.Heading = new_heading - } else { - log.Printf("Zero Heading %v", new_heading) - } + log.Printf("Zero Heading %v", new_heading) } } + } - // We only self repair when we're stopped - if math.Abs(float64(r.Speed)) < v.Epsilon && r.RepairCounter > 0 { - r.RepairCounter -= delta - if r.RepairCounter < 0 { - r.Health += g.repair_hp - r.RepairCounter = g.repair_rate - } + // We only self repair when we're stopped + if math.Abs(float64(r.Speed)) < v.Epsilon && r.RepairCounter > 0 { + r.RepairCounter -= delta + if r.RepairCounter < 0 { + r.Health += g.repair_hp + r.RepairCounter = g.repair_rate } + } - // We only self repair when we're stopped - if math.Abs(float64(r.Speed)) < v.Epsilon && r.ActiveScan { - r.ScanCounter += delta * float32(r.Stats.ScannerRadius) * 0.1 - } else if r.ScanCounter > 0 { - r.ScanCounter -= delta * float32(r.Stats.ScannerRadius) * 0.05 - if r.ScanCounter <= 0 { - r.ScanCounter = 0 - } + // We only self repair when we're stopped + if math.Abs(float64(r.Speed)) < v.Epsilon && r.ActiveScan { + r.ScanCounter += delta * float32(r.Stats.ScannerRadius) * 0.1 + } else if r.ScanCounter > 0 { + r.ScanCounter -= delta * float32(r.Stats.ScannerRadius) * 0.05 + if r.ScanCounter <= 0 { + r.ScanCounter = 0 } + } - if r.FireAt != nil { - proj := r.fire(g.projectiles, g.turn) - if proj != nil { - g.projectiles[proj] = true - } + if r.FireAt != nil { + proj := r.fire(g.projectiles, g.turn) + if proj != nil { + g.projectiles[proj] = true } + } - if r.Probe != nil && r.ProbeResult == nil { - probe_vector := r.Probe.Sub(r.Position) - coll, pos, _ := r.checkCollisions(g, probe_vector) - if coll { - r.ProbeResult = &pos - } + if r.Probe != nil && r.ProbeResult == nil { + probe_vector := r.Probe.Sub(r.Position) + coll, pos, _ := r.checkCollisions(g, probe_vector) + if coll { + r.ProbeResult = &pos } } } @@ -311,6 +312,8 @@ func (r *Robot) reset(g *game) { r.Position = start_pos r.Health = r.Stats.Hp + log.Printf("Reset %v", r) + // Check Obstacles retry := false for _, obj := range g.obstacles { diff --git a/projectile.go b/projectile.go index 0aa0ad6..1f65cb6 100644 --- a/projectile.go +++ b/projectile.go @@ -24,7 +24,7 @@ func (p *Projectile) Tick(g *game) { hit_player := false for player := range g.players { for _, r := range player.Robots { - if &r == p.Owner { + if r == p.Owner { continue } diff --git a/protocol.go b/protocol.go index d1a115d..0f7a0cd 100644 --- a/protocol.go +++ b/protocol.go @@ -192,7 +192,7 @@ func addPlayer(ws *websocket.Conn) { } p := &player{ - Robots: []Robot{}, + Robots: []*Robot{}, send: make(chan *Boardstate), ws: ws, } @@ -200,14 +200,14 @@ func addPlayer(ws *websocket.Conn) { for _, stats := range conf.Stats { r := Robot{ Stats: DeriveStats(stats), - Id: player_id, + Id: idg.Hash(), Name: clientid.Name, Health: 10, Scanners: make([]Scanner, 0)} r.Health = r.Stats.Hp log.Printf("Adding Robot: %v", r) - p.Robots = append(p.Robots, r) r.reset(game) + p.Robots = append(p.Robots, &r) } game.register <- p diff --git a/robot.go b/robot.go index 344e966..9c5c54c 100644 --- a/robot.go +++ b/robot.go @@ -157,9 +157,4 @@ type Instruction struct { TargetSpeed *float32 `json:"target_speed,omitempty"` Repair *bool `json:"repair,omitempty"` Scan *bool `json:"scan,omitempty"` - Stats Stats `json:"stats"` -} - -type PlayerInstructions struct { - RobotInstructions map[string]Instruction `json:"instructions"` }