server/game.go
2013-11-08 20:04:09 -08:00

301 lines
7.0 KiB
Go

package main
import (
"log"
"sort"
"sync"
"time"
)
const maxPlayer = 128
type BotHealth struct {
RobotId string `json:"robot_id"`
Health int `json:"health"`
}
type Boardstate struct {
MyRobots []Robot `json:"my_robots"`
OtherRobots []OtherRobot `json:"robots"`
Projectiles []Projectile `json:"projectiles"`
Splosions []Splosion `json:"splosions"`
Obstacles []Obstacle `json:"obj"`
Reset bool `json:"reset"`
Type string `json:"type"`
Turn int `json:"turn"`
AllBots []BotHealth `json:"all_bots"`
Messages []string `json:"messages"`
}
func NewBoardstate() *Boardstate {
return &Boardstate{
MyRobots: []Robot{},
OtherRobots: []OtherRobot{},
Projectiles: []Projectile{},
Splosions: []Splosion{},
AllBots: []BotHealth{},
Type: "boardstate",
}
}
type Scanner struct {
Id string `json:"id"`
Type string `json:"type"`
}
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) *game {
ml.Lock()
g, _ := ml.m[id]
ml.Unlock()
return g
}
func (ml *MapLock) add(g *game) {
ml.Lock()
ml.m[g.id] = g
ml.Unlock()
}
type game struct {
id string
players map[*player]bool
projectiles map[*Projectile]bool
splosions map[*Splosion]bool
obstacles []Obstacle
register chan *player
unregister chan *player
turn int
width, height float32
spectators map[*Spectator]bool
sregister chan *Spectator
sunregister chan *Spectator
kill chan bool
repair_hp int
repair_rate float32
}
func NewGame(id string, width, height float32) *game {
g := &game{
id: id,
register: make(chan *player),
unregister: make(chan *player, maxPlayer),
projectiles: make(map[*Projectile]bool),
splosions: make(map[*Splosion]bool),
obstacles: GenerateObstacles(*obstacle_count, width, height),
players: make(map[*player]bool),
turn: 0,
width: width,
height: height,
spectators: make(map[*Spectator]bool),
sregister: make(chan *Spectator),
sunregister: make(chan *Spectator),
kill: make(chan bool, maxPlayer),
repair_hp: 5,
repair_rate: 3.0,
}
return g
}
func (g *game) tick(payload *Boardstate) int {
robots_remaining := 0
payload.Obstacles = g.obstacles
// Update Players
for p := range g.players {
if p.Robot.Health > 0 {
robots_remaining++
p.Tick(g)
}
if len(p.Robot.Message) > 0 {
if len(p.Robot.Message) > 100 {
p.Robot.Message = p.Robot.Message[0:99]
}
payload.Messages = append(payload.Messages, p.Robot.Message)
}
payload.OtherRobots = append(
payload.OtherRobots,
p.Robot.GetTruncatedDetails())
payload.AllBots = append(
payload.AllBots,
BotHealth{RobotId: p.Robot.Id, Health: p.Robot.Health})
}
// Update Projectiles
for pr := range g.projectiles {
pr.Tick(g)
}
// We do this here, because the tick calls can alter g.projectiles
for pr := range g.projectiles {
payload.Projectiles = append(payload.Projectiles, *pr)
}
// Update Splosions
for s := range g.splosions {
s.Tick()
if !s.Alive() {
delete(g.splosions, s)
}
payload.Splosions = append(payload.Splosions, *s)
}
return robots_remaining
}
func (g *game) send_update(payload *Boardstate) {
// Ensure that the robots are always sent in a consistent order
sort.Sort(RobotSorter{Robots: payload.OtherRobots})
sort.Sort(AllRobotSorter{Robots: payload.AllBots})
for p := range g.players {
// Copy the payload but only add the robots in scanner range
player_payload := NewBoardstate()
player_payload.Messages = payload.Messages
player_payload.Splosions = payload.Splosions
player_payload.Obstacles = payload.Obstacles
player_payload.AllBots = payload.AllBots
player_payload.Turn = payload.Turn
player_payload.Reset = payload.Reset
player_payload.MyRobots = append(player_payload.MyRobots, p.Robot)
player_payload.OtherRobots = append(
player_payload.OtherRobots,
p.Robot.GetTruncatedDetails())
player_payload.Projectiles = []Projectile{}
player_payload.Obstacles = []Obstacle{}
if p.Robot.Health > 0 {
// Filter robots by scanner
for player := range g.players {
for _, scan_entry := range p.Robot.Scanners {
if player.Robot.Id == scan_entry.Id {
player_payload.OtherRobots = append(
player_payload.OtherRobots,
player.Robot.GetTruncatedDetails())
}
}
}
// Filter projectiles
for proj := range g.projectiles {
if proj.Owner == p {
player_payload.Projectiles = append(
player_payload.Projectiles,
*proj)
}
for _, scan_entry := range p.Robot.Scanners {
if proj.Id == scan_entry.Id {
player_payload.Projectiles = append(
player_payload.Projectiles,
*proj)
}
}
}
// Filter objects
for _, ob := range g.obstacles {
if ob.distance_from_point(p.Robot.Position) < float32(p.Robot.Stats.ScannerRadius)+p.Robot.ScanCounter {
player_payload.Obstacles = append(
player_payload.Obstacles,
ob)
}
}
} else {
player_payload.OtherRobots = payload.OtherRobots
player_payload.Projectiles = payload.Projectiles
player_payload.Obstacles = payload.Obstacles
}
p.send <- player_payload
}
for s := range g.spectators {
s.send <- payload
}
}
func (g *game) run() {
var t0, t1 time.Time
ticker := time.NewTicker(time.Duration(*tick) * time.Millisecond)
for {
select {
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()
return
case p := <-g.register:
g.players[p] = true
case p := <-g.unregister:
delete(g.players, p)
close(p.send)
case s := <-g.sregister:
g.spectators[s] = true
case s := <-g.sunregister:
delete(g.spectators, s)
close(s.send)
case <-ticker.C:
t0 = time.Now()
payload := NewBoardstate()
g.turn++
payload.Turn = g.turn
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))
}
// UPDATE GAME STATE
robots_remaining := g.tick(payload)
// Determine end game?
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)
log.Printf("game %s: game over", g.id)
}
g.obstacles = GenerateObstacles(5, g.width, g.height)
p.reset(g)
}
payload.Reset = true
} else {
payload.Reset = false
}
t1 = time.Now()
if *verbose {
log.Printf("Turn Processes %v\n", t1.Sub(t0))
}
// SEND THE UPDATE TO EACH PLAYER
g.send_update(payload)
t1 = time.Now()
if *verbose {
log.Printf("Sent Payload %v\n", t1.Sub(t0))
}
}
}
}