server/game.go

291 lines
6.6 KiB
Go
Raw Normal View History

2013-08-19 20:43:26 -07:00
package main
import (
v "bitbucket.org/hackerbots/vector"
// "encoding/json"
"errors"
2013-08-19 20:43:26 -07:00
"log"
"sort"
"sync"
2013-08-19 20:43:26 -07:00
"time"
)
const maxPlayer = 128
2013-09-27 22:27:05 -07:00
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() *Boardstate {
return &Boardstate{
Robots: []Robot{},
Projectiles: []Projectile{},
Type: "boardstate",
}
}
type Scanner struct {
Id string `json:"id"`
2013-09-27 22:27:05 -07:00
Position v.Point2d `json:"position"`
Stats Stats `json:"stats"`
}
type MapLock struct {
m map[string]*game
sync.RWMutex
}
// get is a function that returns a game if found, and creates one if
// not found and force is true. In order to get a hash (rather than use
// the string you pass) send "" for id.
func (ml *MapLock) get(id string, force bool) (*game, error) {
ml.Lock()
g, ok := games.m[id]
ml.Unlock()
if ok {
return g, nil
}
if !force {
return nil, errors.New("game not found")
}
if id == "" {
id = idg.Hash()
}
_g := NewGame(id, float32(*width), float32(*height))
go _g.run()
ml.Lock()
ml.m[id] = _g
ml.Unlock()
return _g, nil
}
2013-08-19 20:43:26 -07:00
type game struct {
2013-09-01 23:00:09 -07:00
id string
2013-09-01 22:04:34 -07:00
players map[*player]bool
2013-09-27 22:27:05 -07:00
projectiles map[*Projectile]bool
splosions map[*Splosion]bool
2013-09-01 22:04:34 -07:00
register chan *player
unregister chan *player
turn int
width, height float32
2013-09-01 22:04:34 -07:00
spectators map[*Spectator]bool
sregister chan *Spectator
sunregister chan *Spectator
kill chan bool
2013-08-19 20:43:26 -07:00
}
func NewGame(id string, width, height float32) *game {
2013-09-04 00:06:39 -07:00
g := &game{
2013-09-01 23:00:09 -07:00
id: id,
2013-08-25 23:10:02 -07:00
register: make(chan *player),
unregister: make(chan *player, maxPlayer),
2013-09-27 22:27:05 -07:00
projectiles: make(map[*Projectile]bool),
splosions: make(map[*Splosion]bool),
2013-08-25 23:10:02 -07:00
players: make(map[*player]bool),
turn: 0,
2013-09-01 22:04:34 -07:00
width: width,
height: height,
2013-08-25 23:10:02 -07:00
spectators: make(map[*Spectator]bool),
sregister: make(chan *Spectator),
sunregister: make(chan *Spectator),
kill: make(chan bool, maxPlayer),
2013-08-25 23:10:02 -07:00
}
2013-09-04 00:06:39 -07:00
return g
}
func (g *game) run() {
2013-09-05 23:31:13 -07:00
var t0, t1 time.Time
2013-10-01 21:37:53 -07:00
ticker := time.NewTicker(time.Duration(*tick) * time.Millisecond)
2013-08-19 20:43:26 -07:00
for {
select {
2013-08-28 22:16:55 -07:00
case <-g.kill:
log.Printf("game %s: received kill signal, dying gracefully", g.id)
games.Lock()
for player := range g.players {
close(player.send)
}
delete(games.m, g.id)
games.Unlock()
2013-08-28 22:16:55 -07:00
return
2013-08-19 20:43:26 -07:00
case p := <-g.register:
g.players[p] = true
case p := <-g.unregister:
delete(g.players, p)
close(p.send)
2013-08-19 22:23:35 -07:00
case s := <-g.sregister:
g.spectators[s] = true
case s := <-g.sunregister:
delete(g.spectators, s)
close(s.send)
2013-10-01 21:37:53 -07:00
case <-ticker.C:
payload := NewBoardstate()
2013-08-19 20:43:26 -07:00
g.turn++
2013-09-07 19:13:12 -07:00
payload.Turn = g.turn
2013-09-05 23:31:13 -07:00
t0 = time.Now()
2013-09-03 23:26:40 -07:00
2013-08-19 20:43:26 -07:00
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))
}
2013-09-03 23:26:40 -07:00
2013-08-19 20:43:26 -07:00
robots_remaining := 0
// UPDATE GAME STATE
2013-08-19 20:43:26 -07:00
for p := range g.players {
if p.Robot.Health > 0 {
robots_remaining++
// TODO: measure if this would be better to go and wait ...
2013-09-04 00:07:47 -07:00
p.scan(g.players)
p.nudge(g)
2013-09-04 00:05:38 -07:00
if p.Robot.FireAt != nil {
proj := p.fire(g.projectiles, g.turn)
2013-09-03 23:26:40 -07:00
if proj != nil {
g.projectiles[proj] = true
}
2013-08-19 20:43:26 -07:00
}
}
payload.Robots = append(payload.Robots, p.Robot)
}
2013-09-27 22:27:05 -07:00
sort.Sort(RobotSorter{Robots: payload.Robots})
2013-08-19 20:43:26 -07:00
payload.Projectiles = append(payload.Projectiles,
g.nudgeProjectiles()...,
)
2013-08-19 20:43:26 -07:00
for s := range g.splosions {
s.Tick()
if !s.Alive() {
delete(g.splosions, s)
}
2013-08-19 20:43:26 -07:00
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)
2013-09-28 13:19:44 -07:00
log.Printf("game %s: game over", g.id)
2013-08-19 20:43:26 -07:00
}
p.reset()
}
payload.Reset = true
} else {
payload.Reset = false
}
2013-09-05 23:31:13 -07:00
t1 = time.Now()
2013-08-19 20:43:26 -07:00
if *verbose {
log.Printf("Turn Processes %v\n", t1.Sub(t0))
}
// SEND THE UPDATE TO EACH PLAYER
2013-08-19 20:43:26 -07:00
for p := range g.players {
// Copy the payload but only add the robots in scanner range
player_payload := NewBoardstate()
player_payload.Projectiles = payload.Projectiles
player_payload.Splosions = payload.Splosions
player_payload.Turn = payload.Turn
player_payload.Reset = payload.Reset
player_payload.Robots = append(player_payload.Robots, p.Robot)
if p.Robot.Health > 0 {
for player := range g.players {
for _, scan_entry := range p.Robot.Scanners {
if player.Robot.Id == scan_entry.Id {
player_payload.Robots = append(player_payload.Robots, player.Robot)
}
}
}
} else {
player_payload.Robots = payload.Robots
}
// x, _ := json.Marshal(player_payload)
// log.Printf("%v", string(x))
p.send <- player_payload
2013-08-19 20:43:26 -07:00
}
2013-08-19 22:23:35 -07:00
for s := range g.spectators {
s.send <- payload
}
2013-08-19 20:43:26 -07:00
t1 = time.Now()
if *verbose {
log.Printf("Sent Payload %v\n", t1.Sub(t0))
}
}
}
}
2013-08-19 20:43:26 -07:00
2013-09-27 22:27:05 -07:00
func (g *game) nudgeProjectiles() (rprojectiles []Projectile) {
rprojectiles = make([]Projectile, 0)
for p := range g.projectiles {
newPos := move(p.Position, p.MoveTo, float32(p.Speed), delta)
hit_player := false
for player := range g.players {
if player.Robot.Id == p.Id {
continue
}
dist := v.Distance(player.Robot.Position, p.Position)
if dist < 5.0 {
hit_player = true
}
}
// Are we going to intersect the destination zone in this tick?
r_dest := v.Rect2d{
A: v.Point2d{X: p.MoveTo.X - 3, Y: p.MoveTo.Y - 3},
B: v.Point2d{X: p.MoveTo.X + 3, Y: p.MoveTo.Y + 3}}
travel := newPos.Sub(p.Position)
arrived, _, _ := v.RectIntersection(r_dest, p.Position, travel)
if arrived || hit_player {
delete(g.projectiles, p)
// Spawn a splosion
2013-09-27 22:27:05 -07:00
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 := v.Distance(player.Robot.Position, p.Position)
if dist < float32(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 {
}
}
}
}
} else {
p.Position.X = newPos.X
p.Position.Y = newPos.Y
rprojectiles = append(rprojectiles, *p)
2013-08-19 20:43:26 -07:00
}
}
return
2013-08-19 20:43:26 -07:00
}