From b495b16065c6f29b9b9a8816d4d6ae9138cb9aed Mon Sep 17 00:00:00 2001 From: Stephen McQuay Date: Tue, 3 Sep 2013 23:26:40 -0700 Subject: [PATCH] refactor for bot --- game.go | 48 +++++--------- http.go | 24 ++++--- player.go | 24 +++---- protocol.go | 84 +++--------------------- protocol_test.go | 23 ++++++- robot.go | 164 +++++++++++++---------------------------------- spectator.go | 3 +- 7 files changed, 121 insertions(+), 249 deletions(-) diff --git a/game.go b/game.go index d734c87..2eb04ba 100644 --- a/game.go +++ b/game.go @@ -1,6 +1,7 @@ package main import ( + "bitbucket.org/hackerbots/bot" "log" "sort" "time" @@ -9,8 +10,8 @@ import ( type game struct { id string players map[*player]bool - projectiles map[*projectile]bool - splosions map[*splosion]bool + projectiles map[*bot.Projectile]bool + splosions map[*bot.Splosion]bool register chan *player unregister chan *player robot_id chan int @@ -27,8 +28,8 @@ func NewGame(id string, width, height float64) *game { id: id, register: make(chan *player), unregister: make(chan *player), - projectiles: make(map[*projectile]bool), - splosions: make(map[*splosion]bool), + projectiles: make(map[*bot.Projectile]bool), + splosions: make(map[*bot.Splosion]bool), players: make(map[*player]bool), turn: 0, width: width, @@ -40,29 +41,6 @@ func NewGame(id string, width, height float64) *game { } } -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.robot_id = make(chan int) go func() { @@ -88,13 +66,15 @@ func (g *game) run() { 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) + + payload := bot.NewBoardstate(g.turn) robots_remaining := 0 @@ -103,21 +83,25 @@ func (g *game) run() { robots_remaining++ p.scan() p.nudge() + // XXX: change to pointer, check for pointer as (0, 0) is valid target if p.Robot.FireAt.X != 0 && p.Robot.FireAt.Y != 0 { - p.fire() + proj := p.fire() + if proj != nil { + g.projectiles[proj] = true + } } } payload.Robots = append(payload.Robots, p.Robot) } - sort.Sort(robotSorter{payload.Robots}) + sort.Sort(bot.RobotSorter{payload.Robots}) for p := range g.projectiles { - p.nudge() + // XXX: p.nudge() payload.Projectiles = append(payload.Projectiles, *p) } for s := range g.splosions { - s.tick() + // XXX: s.tick() payload.Splosions = append(payload.Splosions, *s) } diff --git a/http.go b/http.go index 20410b9..ef46494 100644 --- a/http.go +++ b/http.go @@ -1,6 +1,7 @@ package main import ( + "bitbucket.org/hackerbots/bot" "code.google.com/p/go.net/websocket" "encoding/json" "fmt" @@ -23,6 +24,7 @@ func startGame(w http.ResponseWriter, req *http.Request) { gameLock.Lock() games[new_game_name] = _g + log.Printf("%+v", games) gameLock.Unlock() w.Write([]byte(fmt.Sprintf(`{"id": "%s"}`, new_game_name))) @@ -42,7 +44,7 @@ func listGames(w http.ResponseWriter, req *http.Request) { func addPlayer(ws *websocket.Conn) { // route to appropriate game ... - var gid GameID + var gid bot.GameID err := websocket.JSON.Receive(ws, &gid) if err != nil { log.Println("problem parsing the requested game id") @@ -50,11 +52,15 @@ func addPlayer(ws *websocket.Conn) { } gameLock.Lock() - game := games[gid.Id] + game, ok := games[gid.Id] gameLock.Unlock() - log.Printf("%+v", game) - id := "asdf" + if !ok { + log.Println("ERROR: game not found") + return + } + + id := idg.Hash() conf, err := Negociate(ws, id, 400, 800) if err != nil { @@ -63,15 +69,17 @@ func addPlayer(ws *websocket.Conn) { if conf != nil { p := &player{ - Robot: robot{ + Robot: bot.Robot{ Stats: conf.Stats, Id: id, Health: conf.Stats.Hp, - Scanners: make([]scanner, 0)}, - send: make(chan *boardstate), + Scanners: make([]bot.Scanner, 0)}, + send: make(chan *bot.Boardstate), ws: ws, + game: game, } p.reset() + log.Printf("game: %+v", game) game.register <- p defer func() { game.unregister <- p @@ -81,7 +89,7 @@ func addPlayer(ws *websocket.Conn) { log.Printf("%v has been disconnect from this game\n", p.Robot.Id) } else { s := &Spectator{ - send: make(chan *boardstate), + send: make(chan *bot.Boardstate), ws: ws, } game.sregister <- s diff --git a/player.go b/player.go index fa6d2b9..f432360 100644 --- a/player.go +++ b/player.go @@ -1,6 +1,7 @@ package main import ( + "bitbucket.org/hackerbots/bot" v "bitbucket.org/hackerbots/vector" "code.google.com/p/go.net/websocket" "log" @@ -9,15 +10,16 @@ import ( type player struct { ws *websocket.Conn - Robot robot - send chan *boardstate + game *game + Robot bot.Robot + send chan *bot.Boardstate Instruction instruction } type instruction struct { MoveTo *v.Point2d `json:"move_to,omitempty"` FireAt *v.Point2d `json:"fire_at,omitempty"` - Stats stats `json:"stats"` + Stats bot.Stats `json:"stats"` } func (p *player) sender() { @@ -60,14 +62,14 @@ func (p *player) nudge() { } func (p *player) scan() { - p.Robot.Scanners = make([]scanner, 0) - for player := range g.players { + p.Robot.Scanners = make([]bot.Scanner, 0) + for player := range p.game.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{ + s := bot.Scanner{ Position: v.Point2d{ X: player.Robot.Position.X, Y: player.Robot.Position.Y, @@ -78,15 +80,14 @@ func (p *player) scan() { } } -func (p *player) fire() { - - for proj := range g.projectiles { +func (p *player) fire() *bot.Projectile { + for proj := range p.game.projectiles { if proj.Id == p.Robot.Id { - return + return nil } } - proj := &projectile{ + return &bot.Projectile{ Id: p.Robot.Id, Position: p.Robot.Position, MoveTo: p.Robot.FireAt, @@ -94,7 +95,6 @@ func (p *player) fire() { Radius: p.Robot.Stats.WeaponRadius, Speed: float64(p.Robot.Stats.Speed * 2), } - g.projectiles[proj] = true } func (p *player) reset() { diff --git a/protocol.go b/protocol.go index 49848f6..d04214f 100644 --- a/protocol.go +++ b/protocol.go @@ -1,77 +1,11 @@ package main import ( + "bitbucket.org/hackerbots/bot" "code.google.com/p/go.net/websocket" "errors" ) -type GameID struct { - Id string `json:"id"` -} - -// > identify -type IdRequest struct { - Type string `json:"type"` - AssignedID string `json:"id"` -} - -func NewIdRequest(id string) *IdRequest { - return &IdRequest{ - Type: "idreq", - AssignedID: id, - } -} - -// < [robot | spectator], name, client-type, game ID -type ClientID struct { - Type string `json:"type"` - Name string `json:"name"` - Useragent string `json:"useragent"` -} - -func (c *ClientID) Valid() (bool, string) { - switch c.Type { - case "robot", "spectator": - return true, "" - } - return false, "usergent must be 'robot' or 'spectator'" -} - -type BoardSize struct { - Width float64 `json:"width"` - Height float64 `json:"height"` -} - -type GameParam struct { - BoardSize BoardSize `json:"boardsize"` - Type string `json:"type"` -} - -// > [OK | FULL | NOT AUTH], board size, game params -func NewGameParam(w, h float64) *GameParam { - return &GameParam{ - BoardSize: BoardSize{ - Width: w, - Height: h, - }, - Type: "gameparam", - } -} - -type Handshake struct { - ID string `json:"id"` - Success bool `json:"success"` - Type string `json:"type"` -} - -func NewHandshake(id string, success bool) *Handshake { - return &Handshake{ - ID: id, - Success: success, - Type: "handshake", - } -} - type Failure struct { Reason string `json:"reason"` Type string `json:"type"` @@ -84,15 +18,15 @@ func NewFailure(reason string) *Failure { } } -func Negociate(ws *websocket.Conn, id string, width, height float64) (*Config, error) { +func Negociate(ws *websocket.Conn, id string, width, height float64) (*bot.Config, error) { var err error - err = websocket.JSON.Send(ws, NewIdRequest(id)) + err = websocket.JSON.Send(ws, bot.NewIdRequest(id)) if err != nil { return nil, errors.New("generic servr error") } - var clientid ClientID + var clientid bot.ClientID err = websocket.JSON.Receive(ws, &clientid) if err != nil { return nil, errors.New("could not parse id") @@ -105,7 +39,7 @@ func Negociate(ws *websocket.Conn, id string, width, height float64) (*Config, e return nil, errors.New(msg) } - gameParam := NewGameParam(width, height) + gameParam := bot.NewGameParam(width, height) err = websocket.JSON.Send(ws, gameParam) if err != nil { websocket.JSON.Send(ws, NewFailure("generic server error")) @@ -113,18 +47,18 @@ func Negociate(ws *websocket.Conn, id string, width, height float64) (*Config, e } switch clientid.Type { case "robot": - var conf Config + var conf bot.Config for { err = websocket.JSON.Receive(ws, &conf) if err != nil { return nil, err } // TODO: verify conf's type - if conf.Stats.valid() { - _ = websocket.JSON.Send(ws, NewHandshake(id, true)) + if conf.Stats.Valid() { + _ = websocket.JSON.Send(ws, bot.NewHandshake(id, true)) break } else { - _ = websocket.JSON.Send(ws, NewHandshake(id, false)) + _ = websocket.JSON.Send(ws, bot.NewHandshake(id, false)) } } return &conf, nil diff --git a/protocol_test.go b/protocol_test.go index ec508b7..a62fead 100644 --- a/protocol_test.go +++ b/protocol_test.go @@ -3,6 +3,7 @@ package main import ( "code.google.com/p/go.net/websocket" "encoding/json" + "log" "net/http" "testing" ) @@ -17,6 +18,10 @@ type clientIDTest struct { expected result } +type CreatedGame struct { + Id string `json:"id"` +} + var clientIDTests = []clientIDTest{ {ClientID{Type: "robot"}, result{true, ""}}, {ClientID{Type: "spectator"}, result{true, ""}}, @@ -52,11 +57,13 @@ var handled bool func DummyServer() (*websocket.Conn, error) { if !handled { http.Handle("/ws/", websocket.Handler(addPlayer)) + http.Handle("/game/start/", JsonHandler(startGame)) handled = true } - g = NewGame("hello world", 400, 800) - go g.run() + games = make(map[string]*game) + idg = NewIdGenerator() + go http.ListenAndServe(*addr, nil) origin := "http://localhost/" @@ -66,6 +73,18 @@ func DummyServer() (*websocket.Conn, error) { func TestGoodProtocol(t *testing.T) { ws, err := DummyServer() + log.Printf("blah: %+v", ws) + + resp, err := http.Get("http://localhost:8666/game/start/") + if err != nil { + t.Errorf("error getting: %+v", err) + } + defer resp.Body.Close() + var g CreatedGame + if err := json.NewDecoder(resp.Body).Decode(&g); err != nil { + t.Errorf("json parse error? %+v", err) + } + log.Printf("g: %+v\n", g) err = websocket.JSON.Send(ws, GameID{ "test", diff --git a/robot.go b/robot.go index 1b806af..db8c254 100644 --- a/robot.go +++ b/robot.go @@ -1,126 +1,52 @@ package main -import ( - v "bitbucket.org/hackerbots/vector" -) - type weapon struct { Strength float64 `json:"strength"` Radius float64 `json:"radius"` } -type stats struct { - Hp int `json:"hp"` - Speed float64 `json:"speed"` - 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 - 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 - if player.Robot.Health <= 0 { - } - } - } - } - } - 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) - } -} +// XXX: this needs to go into game somehow +// func (p *bot.Projectile) nudge() { +// newPos := move(p.Position, p.MoveTo, float64(p.Speed), delta) +// +// hit_player := false +// for player := range p.game.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(p.game.projectiles, p) +// +// // Spawn a splosion +// splo := &splosion{ +// Id: p.Id, +// Position: p.Position, +// Radius: p.Radius, +// MaxDamage: 10, +// MinDamage: 5, +// Lifespan: 8, +// } +// p.game.splosions[splo] = true +// +// for player := range p.game.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 +// if player.Robot.Health <= 0 { +// } +// } +// } +// } +// } +// p.Position.X = newPos.X +// p.Position.Y = newPos.Y +// } diff --git a/spectator.go b/spectator.go index 055a4b1..b25176d 100644 --- a/spectator.go +++ b/spectator.go @@ -1,12 +1,13 @@ package main import ( + "bitbucket.org/hackerbots/bot" "code.google.com/p/go.net/websocket" ) type Spectator struct { ws *websocket.Conn - send chan *boardstate + send chan *bot.Boardstate } func (s *Spectator) sender() {