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
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)
}

24
http.go
View File

@ -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

View File

@ -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() {

View File

@ -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

View File

@ -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",

164
robot.go
View File

@ -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
// }

View File

@ -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() {