From 3b183481ef615a0fc6c40a48942226e85ae034c9 Mon Sep 17 00:00:00 2001 From: Stephen McQuay Date: Mon, 19 Aug 2013 20:43:26 -0700 Subject: [PATCH] added rest of game in current state --- game.go | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++ geom.go | 18 ++++++++ main.go | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ player.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++ robot.go | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 511 insertions(+) create mode 100644 game.go create mode 100644 geom.go create mode 100644 main.go create mode 100644 player.go create mode 100644 robot.go diff --git a/game.go b/game.go new file mode 100644 index 0000000..7514bf5 --- /dev/null +++ b/game.go @@ -0,0 +1,122 @@ +package main + +import ( + "log" + "sort" + "time" +) + +type game struct { + players map[*player]bool + projectiles map[*projectile]bool + splosions map[*splosion]bool + register chan *player + unregister chan *player + id chan int + turn int +} + +type Config struct { + ID string `json:"id"` + Stats stats `json:"stats"` +} + +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(id int) *boardstate { + return &boardstate{ + Robots: []robot{}, + Projectiles: []projectile{}, + Type: "boardstate", + Turn: id, + } +} + +func (g *game) run() { + g.id = make(chan int) + go func() { + for i := 0; ; i++ { + g.id <- i + } + }() + + for { + select { + case p := <-g.register: + log.Printf("adding player: %+v", p.Robot.Id) + g.players[p] = true + case p := <-g.unregister: + delete(g.players, p) + close(p.send) + case <-time.Tick(time.Duration(*tick) * time.Millisecond): + 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)) + } + payload := NewBoardstate(g.turn) + + robots_remaining := 0 + + for p := range g.players { + if p.Robot.Health > 0 { + robots_remaining++ + p.scan() + p.nudge() + if p.Robot.FireAt.X != 0 && p.Robot.FireAt.Y != 0 { + p.fire() + } + } + payload.Robots = append(payload.Robots, p.Robot) + } + sort.Sort(robotSorter{payload.Robots}) + + for p := range g.projectiles { + p.nudge() + payload.Projectiles = append(payload.Projectiles, *p) + } + + for s := range g.splosions { + s.tick() + 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) + } + p.reset() + } + payload.Reset = true + } else { + payload.Reset = false + } + + t1 := time.Now() + if *verbose { + log.Printf("Turn Processes %v\n", t1.Sub(t0)) + } + + for p := range g.players { + p.send <- payload + } + + t1 = time.Now() + if *verbose { + log.Printf("Sent Payload %v\n", t1.Sub(t0)) + } + + } + } +} diff --git a/geom.go b/geom.go new file mode 100644 index 0000000..cce2b35 --- /dev/null +++ b/geom.go @@ -0,0 +1,18 @@ +package main + +import ( + v "bitbucket.org/hackerbots/vector" +) + +const threshold = 1e-7 + +func distance(p1, p2 v.Point2d) float64 { + return p1.Sub(p2).Mag() +} + +func move(d1, d2 v.Point2d, velocity float64, timeDelta float64) v.Point2d { + v := d2.Sub(d1) + v_norm := v.Normalize() + v_scaled := v_norm.Scale(velocity * timeDelta) + return d1.Add(v_scaled) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..da5e227 --- /dev/null +++ b/main.go @@ -0,0 +1,130 @@ +package main + +import ( + "code.google.com/p/go.net/websocket" + "flag" + "fmt" + "bitbucket.org/hackerbots/botserv/protocol" + v "bitbucket.org/hackerbots/vector" + "log" + "math/rand" + "net/http" + "time" +) + +var addr = flag.String("addr", ":8666", "http service address") +var velocity = flag.Float64("velocity", 30, "") +var tick = flag.Int("tick", 33, "") +var weapon_radius = flag.Int("weapon_radius", 35, "") +var verbose = flag.Bool("verbose", false, "") +var width = flag.Float64("width", 800, "width of field") +var height = flag.Float64("height", 550, "height of field") + +var delta float64 +var g = game{ + register: make(chan *player), + unregister: make(chan *player), + projectiles: make(map[*projectile]bool), + splosions: make(map[*splosion]bool), + players: make(map[*player]bool), + turn: 0, +} + +func main() { + rand.Seed(time.Now().UnixNano()) + flag.Parse() + + delta = float64(*tick) / 1000 + + http.Handle("/ws/", websocket.Handler(addPlayer)) + + go g.run() + + err := http.ListenAndServe(*addr, nil) + if err != nil { + log.Fatal("unable to start server") + } +} + +func addPlayer(ws *websocket.Conn) { + var err error + + id := fmt.Sprintf("robot%d", <-g.id) + log.Printf("sending robot id: %s", id) + + err = websocket.JSON.Send(ws, protocol.NewIdRequest(id)) + if err != nil { + log.Printf("%s: problem sending initial identification", id) + websocket.JSON.Send(ws, protocol.NewFailure("generic server error")) + return + } + + var clientid protocol.ClientID + err = websocket.JSON.Receive(ws, &clientid) + if err != nil { + log.Printf("%s: problem parsing clientID", id) + websocket.JSON.Send(ws, protocol.NewFailure("could not parse id")) + return + } + if v, msg := clientid.Valid(); !v { + log.Printf("%s: invalid clientid: %+v", id, clientid) + websocket.JSON.Send( + ws, + protocol.NewFailure(msg), + ) + return + } + + gameParam := protocol.NewGameParam(*width, *height) + err = websocket.JSON.Send(ws, gameParam) + if err != nil { + log.Println("%s: problem sending game info: %+v", id, gameParam) + websocket.JSON.Send(ws, protocol.NewFailure("generic server error")) + return + } + + var conf Config + for { + err = websocket.JSON.Receive(ws, &conf) + if err != nil { + log.Print(err) + return + } + if conf.Stats.valid() { + _ = websocket.JSON.Send(ws, protocol.NewHandshake(id, true)) + break + } else { + _ = websocket.JSON.Send(ws, protocol.NewHandshake(id, false)) + log.Printf("%s invalid config", id) + } + } + log.Printf("%s eventually sent valid config", id) + + start_pos := v.Point2d{ + X: rand.Float64() * *width, + Y: rand.Float64() * *height, + } + p := &player{ + Robot: robot{ + Stats: stats{ + Speed: *velocity, + Hp: 200, + WeaponRadius: 35, + ScannerRadius: 200, + }, + Position: start_pos, + MoveTo: start_pos, + Id: id, + Health: 200, + Scanners: make([]scanner, 0)}, + send: make(chan *boardstate), + ws: ws, + } + g.register <- p + defer func() { + g.unregister <- p + }() + go p.sender() + p.recv() + log.Printf("%v has been disconnect from this game\n", p.Robot.Id) +} diff --git a/player.go b/player.go new file mode 100644 index 0000000..11ee71d --- /dev/null +++ b/player.go @@ -0,0 +1,111 @@ +package main + +import ( + "code.google.com/p/go.net/websocket" + v "bitbucket.org/hackerbots/vector" + "log" + "math/rand" +) + +type player struct { + ws *websocket.Conn + Robot robot + send chan *boardstate + Instruction instruction +} + +type instruction struct { + MoveTo *v.Point2d `json:"move_to,omitempty"` + FireAt *v.Point2d `json:"fire_at,omitempty"` + Stats stats `json:"stats"` +} + +func (p *player) sender() { + for things := range p.send { + // log.Printf("%v\n", things) + err := websocket.JSON.Send(p.ws, *things) + if err != nil { + break + } + } + p.ws.Close() +} + +func (p *player) recv() { + for { + var msg instruction + err := websocket.JSON.Receive(p.ws, &msg) + if err != nil { + log.Print(err) + break + } + if msg.MoveTo != nil { + p.Robot.MoveTo = *msg.MoveTo + } + if msg.FireAt != nil { + p.Robot.FireAt = *msg.FireAt + } + if msg.Stats.Speed > 0 { + p.Robot.Stats = msg.Stats + p.Robot.Health = p.Robot.Stats.Hp + log.Printf("%+v", p.Robot.Stats) + } + } + p.ws.Close() +} + +func (p *player) nudge() { + newPos := move(p.Robot.Position, p.Robot.MoveTo, p.Robot.Stats.Speed, delta) + p.Robot.Position.X = newPos.X + p.Robot.Position.Y = newPos.Y +} + +func (p *player) scan() { + p.Robot.Scanners = make([]scanner, 0) + for player := range g.players { + if player.Robot.Id == p.Robot.Id || player.Robot.Health <= 0 { + continue + } + dist := distance(player.Robot.Position, p.Robot.Position) + if dist < float64(p.Robot.Stats.ScannerRadius) { + s := scanner{ + Position: v.Point2d{ + X: player.Robot.Position.X, + Y: player.Robot.Position.Y, + }, + } + p.Robot.Scanners = append(p.Robot.Scanners, s) + } + } +} + +func (p *player) fire() { + + for proj := range g.projectiles { + if proj.Id == p.Robot.Id { + return + } + } + + // log.Printf("%v Fired at %v %v", p.Robot.Id, p.Robot.FireAt.X, p.Robot.FireAt.Y) + + proj := &projectile{ + Id: p.Robot.Id, + Position: p.Robot.Position, + MoveTo: p.Robot.FireAt, + Damage: 10, + Radius: p.Robot.Stats.WeaponRadius, + Speed: float64(p.Robot.Stats.Speed * 2), + } + g.projectiles[proj] = true +} + +func (p *player) reset() { + start_pos := v.Point2d{ + X: rand.Float64() * 800, + Y: rand.Float64() * 550, + } + p.Robot.MoveTo = start_pos + p.Robot.Position = start_pos + p.Robot.Health = p.Robot.Stats.Hp +} diff --git a/robot.go b/robot.go new file mode 100644 index 0000000..f9ccc70 --- /dev/null +++ b/robot.go @@ -0,0 +1,130 @@ +package main + +import ( + v "bitbucket.org/hackerbots/vector" + "log" +) + +type weapon struct { + Strength float64 `json:"strength"` + Radius float64 `json:"radius"` +} + +type stats struct { + Speed float64 `json:"speed"` + Hp int `json:"hp"` + WeaponRadius int `json:"weapon_radius"` + ScannerRadius int `json:"scanner_radius"` +} + +func (s stats) valid() bool { + total := int(s.Speed) + s.Hp + s.WeaponRadius + s.ScannerRadius + log.Printf("total: %d", total) + if total > 500 { + return false + } + return true +} + +type scanner struct { + Position v.Point2d `json:"position"` + Stats stats `json:"stats"` +} + +type robot struct { + Id string `json:"id"` + Stats stats `json:"stats"` + Health int `json:"health"` + Position v.Point2d `json:"position"` + MoveTo v.Point2d `json:"move_to"` + FireAt v.Point2d `json:"fire_at"` + Scanners []scanner `json:"scanners"` +} + +type robotSorter struct { + robots []robot +} + +func (s robotSorter) Len() int { + return len(s.robots) +} + +func (s robotSorter) Swap(i, j int) { + s.robots[i], s.robots[j] = s.robots[j], s.robots[i] +} + +func (s robotSorter) Less(i, j int) bool { + return s.robots[i].Id < s.robots[j].Id +} + +type projectile struct { + Id string `json:"id"` + Position v.Point2d `json:"position"` + MoveTo v.Point2d `json:"move_to"` + Radius int `json:"radius"` + Speed float64 `json:"speed"` + Damage int `json:"damage"` +} + +func (p *projectile) nudge() { + newPos := move(p.Position, p.MoveTo, float64(p.Speed), delta) + + hit_player := false + for player := range g.players { + if player.Robot.Id == p.Id { + continue + } + dist := distance(player.Robot.Position, p.Position) + if dist < 5.0 { + hit_player = true + } + } + + if distance(p.Position, p.MoveTo) < 5 || 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 := distance(player.Robot.Position, p.Position) + if dist < float64(p.Radius) { + + // TODO map damage Max to Min based on distance from explosion + if player.Robot.Health > 0 { + player.Robot.Health -= p.Damage + // log.Printf("Robot %+v is injured", player.Robot) + if player.Robot.Health <= 0 { + // log.Printf("Robot %+v is dead", player.Robot) + } + } + } + } + } + p.Position.X = newPos.X + p.Position.Y = newPos.Y +} + +type splosion struct { + Id string `json:"id"` + Position v.Point2d `json:"position"` + Radius int `json:"radius"` + MaxDamage int `json:"damage"` + MinDamage int `json:"damage"` + Lifespan int `json:"lifespan"` +} + +func (s *splosion) tick() { + s.Lifespan-- + if s.Lifespan <= 0 { + delete(g.splosions, s) + } +}