refactor for bot

This commit is contained in:
Stephen McQuay 2013-09-03 23:26:40 -07:00
parent 835303c53d
commit b495b16065
7 changed files with 121 additions and 249 deletions

48
game.go
View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"bitbucket.org/hackerbots/bot"
"log" "log"
"sort" "sort"
"time" "time"
@ -9,8 +10,8 @@ import (
type game struct { type game struct {
id string id string
players map[*player]bool players map[*player]bool
projectiles map[*projectile]bool projectiles map[*bot.Projectile]bool
splosions map[*splosion]bool splosions map[*bot.Splosion]bool
register chan *player register chan *player
unregister chan *player unregister chan *player
robot_id chan int robot_id chan int
@ -27,8 +28,8 @@ func NewGame(id string, width, height float64) *game {
id: id, id: id,
register: make(chan *player), register: make(chan *player),
unregister: make(chan *player), unregister: make(chan *player),
projectiles: make(map[*projectile]bool), projectiles: make(map[*bot.Projectile]bool),
splosions: make(map[*splosion]bool), splosions: make(map[*bot.Splosion]bool),
players: make(map[*player]bool), players: make(map[*player]bool),
turn: 0, turn: 0,
width: width, 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() { func (g *game) run() {
g.robot_id = make(chan int) g.robot_id = make(chan int)
go func() { go func() {
@ -88,13 +66,15 @@ func (g *game) run() {
case <-time.Tick(time.Duration(*tick) * time.Millisecond): case <-time.Tick(time.Duration(*tick) * time.Millisecond):
g.turn++ g.turn++
t0 := time.Now() t0 := time.Now()
if *verbose { if *verbose {
log.Printf("\033[2JTurn: %v", g.turn) log.Printf("\033[2JTurn: %v", g.turn)
log.Printf("Players: %v", len(g.players)) log.Printf("Players: %v", len(g.players))
log.Printf("Projectiles: %v", len(g.projectiles)) log.Printf("Projectiles: %v", len(g.projectiles))
log.Printf("Explosions: %v", len(g.splosions)) log.Printf("Explosions: %v", len(g.splosions))
} }
payload := NewBoardstate(g.turn)
payload := bot.NewBoardstate(g.turn)
robots_remaining := 0 robots_remaining := 0
@ -103,21 +83,25 @@ func (g *game) run() {
robots_remaining++ robots_remaining++
p.scan() p.scan()
p.nudge() 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 { 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) payload.Robots = append(payload.Robots, p.Robot)
} }
sort.Sort(robotSorter{payload.Robots}) sort.Sort(bot.RobotSorter{payload.Robots})
for p := range g.projectiles { for p := range g.projectiles {
p.nudge() // XXX: p.nudge()
payload.Projectiles = append(payload.Projectiles, *p) payload.Projectiles = append(payload.Projectiles, *p)
} }
for s := range g.splosions { for s := range g.splosions {
s.tick() // XXX: s.tick()
payload.Splosions = append(payload.Splosions, *s) payload.Splosions = append(payload.Splosions, *s)
} }

24
http.go
View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"bitbucket.org/hackerbots/bot"
"code.google.com/p/go.net/websocket" "code.google.com/p/go.net/websocket"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -23,6 +24,7 @@ func startGame(w http.ResponseWriter, req *http.Request) {
gameLock.Lock() gameLock.Lock()
games[new_game_name] = _g games[new_game_name] = _g
log.Printf("%+v", games)
gameLock.Unlock() gameLock.Unlock()
w.Write([]byte(fmt.Sprintf(`{"id": "%s"}`, new_game_name))) 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) { func addPlayer(ws *websocket.Conn) {
// route to appropriate game ... // route to appropriate game ...
var gid GameID var gid bot.GameID
err := websocket.JSON.Receive(ws, &gid) err := websocket.JSON.Receive(ws, &gid)
if err != nil { if err != nil {
log.Println("problem parsing the requested game id") log.Println("problem parsing the requested game id")
@ -50,11 +52,15 @@ func addPlayer(ws *websocket.Conn) {
} }
gameLock.Lock() gameLock.Lock()
game := games[gid.Id] game, ok := games[gid.Id]
gameLock.Unlock() 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) conf, err := Negociate(ws, id, 400, 800)
if err != nil { if err != nil {
@ -63,15 +69,17 @@ func addPlayer(ws *websocket.Conn) {
if conf != nil { if conf != nil {
p := &player{ p := &player{
Robot: robot{ Robot: bot.Robot{
Stats: conf.Stats, Stats: conf.Stats,
Id: id, Id: id,
Health: conf.Stats.Hp, Health: conf.Stats.Hp,
Scanners: make([]scanner, 0)}, Scanners: make([]bot.Scanner, 0)},
send: make(chan *boardstate), send: make(chan *bot.Boardstate),
ws: ws, ws: ws,
game: game,
} }
p.reset() p.reset()
log.Printf("game: %+v", game)
game.register <- p game.register <- p
defer func() { defer func() {
game.unregister <- p 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) log.Printf("%v has been disconnect from this game\n", p.Robot.Id)
} else { } else {
s := &Spectator{ s := &Spectator{
send: make(chan *boardstate), send: make(chan *bot.Boardstate),
ws: ws, ws: ws,
} }
game.sregister <- s game.sregister <- s

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"bitbucket.org/hackerbots/bot"
v "bitbucket.org/hackerbots/vector" v "bitbucket.org/hackerbots/vector"
"code.google.com/p/go.net/websocket" "code.google.com/p/go.net/websocket"
"log" "log"
@ -9,15 +10,16 @@ import (
type player struct { type player struct {
ws *websocket.Conn ws *websocket.Conn
Robot robot game *game
send chan *boardstate Robot bot.Robot
send chan *bot.Boardstate
Instruction instruction Instruction instruction
} }
type instruction struct { type instruction struct {
MoveTo *v.Point2d `json:"move_to,omitempty"` MoveTo *v.Point2d `json:"move_to,omitempty"`
FireAt *v.Point2d `json:"fire_at,omitempty"` FireAt *v.Point2d `json:"fire_at,omitempty"`
Stats stats `json:"stats"` Stats bot.Stats `json:"stats"`
} }
func (p *player) sender() { func (p *player) sender() {
@ -60,14 +62,14 @@ func (p *player) nudge() {
} }
func (p *player) scan() { func (p *player) scan() {
p.Robot.Scanners = make([]scanner, 0) p.Robot.Scanners = make([]bot.Scanner, 0)
for player := range g.players { for player := range p.game.players {
if player.Robot.Id == p.Robot.Id || player.Robot.Health <= 0 { if player.Robot.Id == p.Robot.Id || player.Robot.Health <= 0 {
continue continue
} }
dist := distance(player.Robot.Position, p.Robot.Position) dist := distance(player.Robot.Position, p.Robot.Position)
if dist < float64(p.Robot.Stats.ScannerRadius) { if dist < float64(p.Robot.Stats.ScannerRadius) {
s := scanner{ s := bot.Scanner{
Position: v.Point2d{ Position: v.Point2d{
X: player.Robot.Position.X, X: player.Robot.Position.X,
Y: player.Robot.Position.Y, Y: player.Robot.Position.Y,
@ -78,15 +80,14 @@ func (p *player) scan() {
} }
} }
func (p *player) fire() { func (p *player) fire() *bot.Projectile {
for proj := range p.game.projectiles {
for proj := range g.projectiles {
if proj.Id == p.Robot.Id { if proj.Id == p.Robot.Id {
return return nil
} }
} }
proj := &projectile{ return &bot.Projectile{
Id: p.Robot.Id, Id: p.Robot.Id,
Position: p.Robot.Position, Position: p.Robot.Position,
MoveTo: p.Robot.FireAt, MoveTo: p.Robot.FireAt,
@ -94,7 +95,6 @@ func (p *player) fire() {
Radius: p.Robot.Stats.WeaponRadius, Radius: p.Robot.Stats.WeaponRadius,
Speed: float64(p.Robot.Stats.Speed * 2), Speed: float64(p.Robot.Stats.Speed * 2),
} }
g.projectiles[proj] = true
} }
func (p *player) reset() { func (p *player) reset() {

View File

@ -1,77 +1,11 @@
package main package main
import ( import (
"bitbucket.org/hackerbots/bot"
"code.google.com/p/go.net/websocket" "code.google.com/p/go.net/websocket"
"errors" "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 { type Failure struct {
Reason string `json:"reason"` Reason string `json:"reason"`
Type string `json:"type"` 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 var err error
err = websocket.JSON.Send(ws, NewIdRequest(id)) err = websocket.JSON.Send(ws, bot.NewIdRequest(id))
if err != nil { if err != nil {
return nil, errors.New("generic servr error") return nil, errors.New("generic servr error")
} }
var clientid ClientID var clientid bot.ClientID
err = websocket.JSON.Receive(ws, &clientid) err = websocket.JSON.Receive(ws, &clientid)
if err != nil { if err != nil {
return nil, errors.New("could not parse id") 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) return nil, errors.New(msg)
} }
gameParam := NewGameParam(width, height) gameParam := bot.NewGameParam(width, height)
err = websocket.JSON.Send(ws, gameParam) err = websocket.JSON.Send(ws, gameParam)
if err != nil { if err != nil {
websocket.JSON.Send(ws, NewFailure("generic server error")) 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 { switch clientid.Type {
case "robot": case "robot":
var conf Config var conf bot.Config
for { for {
err = websocket.JSON.Receive(ws, &conf) err = websocket.JSON.Receive(ws, &conf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO: verify conf's type // TODO: verify conf's type
if conf.Stats.valid() { if conf.Stats.Valid() {
_ = websocket.JSON.Send(ws, NewHandshake(id, true)) _ = websocket.JSON.Send(ws, bot.NewHandshake(id, true))
break break
} else { } else {
_ = websocket.JSON.Send(ws, NewHandshake(id, false)) _ = websocket.JSON.Send(ws, bot.NewHandshake(id, false))
} }
} }
return &conf, nil return &conf, nil

View File

@ -3,6 +3,7 @@ package main
import ( import (
"code.google.com/p/go.net/websocket" "code.google.com/p/go.net/websocket"
"encoding/json" "encoding/json"
"log"
"net/http" "net/http"
"testing" "testing"
) )
@ -17,6 +18,10 @@ type clientIDTest struct {
expected result expected result
} }
type CreatedGame struct {
Id string `json:"id"`
}
var clientIDTests = []clientIDTest{ var clientIDTests = []clientIDTest{
{ClientID{Type: "robot"}, result{true, ""}}, {ClientID{Type: "robot"}, result{true, ""}},
{ClientID{Type: "spectator"}, result{true, ""}}, {ClientID{Type: "spectator"}, result{true, ""}},
@ -52,11 +57,13 @@ var handled bool
func DummyServer() (*websocket.Conn, error) { func DummyServer() (*websocket.Conn, error) {
if !handled { if !handled {
http.Handle("/ws/", websocket.Handler(addPlayer)) http.Handle("/ws/", websocket.Handler(addPlayer))
http.Handle("/game/start/", JsonHandler(startGame))
handled = true handled = true
} }
g = NewGame("hello world", 400, 800) games = make(map[string]*game)
go g.run() idg = NewIdGenerator()
go http.ListenAndServe(*addr, nil) go http.ListenAndServe(*addr, nil)
origin := "http://localhost/" origin := "http://localhost/"
@ -66,6 +73,18 @@ func DummyServer() (*websocket.Conn, error) {
func TestGoodProtocol(t *testing.T) { func TestGoodProtocol(t *testing.T) {
ws, err := DummyServer() 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{ err = websocket.JSON.Send(ws, GameID{
"test", "test",

164
robot.go
View File

@ -1,126 +1,52 @@
package main package main
import (
v "bitbucket.org/hackerbots/vector"
)
type weapon struct { type weapon struct {
Strength float64 `json:"strength"` Strength float64 `json:"strength"`
Radius float64 `json:"radius"` Radius float64 `json:"radius"`
} }
type stats struct { // XXX: this needs to go into game somehow
Hp int `json:"hp"` // func (p *bot.Projectile) nudge() {
Speed float64 `json:"speed"` // newPos := move(p.Position, p.MoveTo, float64(p.Speed), delta)
WeaponRadius int `json:"weapon_radius"` //
ScannerRadius int `json:"scanner_radius"` // hit_player := false
} // for player := range p.game.players {
// if player.Robot.Id == p.Id {
func (s stats) valid() bool { // continue
total := int(s.Speed) + s.Hp + s.WeaponRadius + s.ScannerRadius // }
if total > 500 { // dist := distance(player.Robot.Position, p.Position)
return false // if dist < 5.0 {
} // hit_player = true
return true // }
} // }
//
type scanner struct { // if distance(p.Position, p.MoveTo) < 5 || hit_player {
Position v.Point2d `json:"position"` // delete(p.game.projectiles, p)
Stats stats `json:"stats"` //
} // // Spawn a splosion
// splo := &splosion{
type robot struct { // Id: p.Id,
Id string `json:"id"` // Position: p.Position,
Stats stats `json:"stats"` // Radius: p.Radius,
Health int `json:"health"` // MaxDamage: 10,
Position v.Point2d `json:"position"` // MinDamage: 5,
MoveTo v.Point2d `json:"move_to"` // Lifespan: 8,
FireAt v.Point2d `json:"fire_at"` // }
Scanners []scanner `json:"scanners"` // p.game.splosions[splo] = true
} //
// for player := range p.game.players {
type robotSorter struct { // dist := distance(player.Robot.Position, p.Position)
robots []robot // if dist < float64(p.Radius) {
} //
// // TODO map damage Max to Min based on distance from explosion
func (s robotSorter) Len() int { // if player.Robot.Health > 0 {
return len(s.robots) // player.Robot.Health -= p.Damage
} // if player.Robot.Health <= 0 {
// }
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 { // p.Position.X = newPos.X
return s.robots[i].Id < s.robots[j].Id // p.Position.Y = newPos.Y
} // }
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)
}
}

View File

@ -1,12 +1,13 @@
package main package main
import ( import (
"bitbucket.org/hackerbots/bot"
"code.google.com/p/go.net/websocket" "code.google.com/p/go.net/websocket"
) )
type Spectator struct { type Spectator struct {
ws *websocket.Conn ws *websocket.Conn
send chan *boardstate send chan *bot.Boardstate
} }
func (s *Spectator) sender() { func (s *Spectator) sender() {