server/game.go
Stephen McQuay 6fd4138740 added bw calcs
unfortunately there is an issue with how these are calculated, and they don't
decrease as things quiet down. Well, the bad numbers are their for your
consumption, and should Just Work TM when appropriate changes in
bitbucket.org/smcquay/bandwith land. And they will. But I'm tired now.
2014-03-03 22:56:26 -08:00

386 lines
8.6 KiB
Go

package main
// delete me
import (
"log"
"sort"
"sync"
"time"
"bitbucket.org/smcquay/bandwidth"
)
const maxPlayer = 128
type BotHealth struct {
RobotId string `json:"robot_id"`
Health int `json:"health"`
}
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 BotStats struct {
Kills int
Deaths int
Suicides int
Shots int
DirectHits int
Hits int
Wins int
}
type PlayerStats struct {
BotStats map[string]*BotStats
Wins int
}
type GameStats struct {
PlayerStats map[string]*PlayerStats
sync.RWMutex
}
type game struct {
id string
players map[*player]bool
projectiles map[*Projectile]bool
splosions map[*Splosion]bool
obstacles []Obstacle
obstacle_count int
register chan *player
unregister chan *player
turn int
players_remaining int
width, height float32
maxPoints int
spectators map[*Spectator]bool
sregister chan *Spectator
sunregister chan *Spectator
kill chan bool
repair_hp int
repair_rate float32
tick_duration int
stats GameStats
mode GameMode
bw *bandwidth.Bandwidth
}
type GameMode interface {
setup(g *game)
tick(gg *game, payload *Boardstate)
gameOver(gg *game) (bool, *GameOver)
}
func NewGame(id string, width, height float32, obstacles, tick, maxPoints int, mode string) (*game, error) {
bw, err := bandwidth.NewBandwidth(
[]int{1, 10, 60},
time.Duration(500)*time.Millisecond,
)
if err != nil {
log.Fatal("seriously, what the fuck")
return nil, err
}
go bw.Run()
g := &game{
id: id,
register: make(chan *player, maxPlayer),
unregister: make(chan *player, maxPlayer),
projectiles: make(map[*Projectile]bool),
splosions: make(map[*Splosion]bool),
obstacles: GenerateObstacles(obstacles, width, height),
obstacle_count: obstacles,
players: make(map[*player]bool),
turn: 0,
width: width,
height: height,
maxPoints: maxPoints,
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,
tick_duration: tick,
players_remaining: 2,
stats: GameStats{PlayerStats: make(map[string]*PlayerStats)},
bw: bw,
}
if mode == "melee" {
g.mode = &melee{respawn: make(map[*Robot]float64)}
} else {
g.mode = &deathmatch{}
}
g.mode.setup(g)
return g, nil
}
func (g *game) tick(payload *Boardstate) {
g.players_remaining = 0
payload.Objects = MinifyObstacles(g.obstacles)
// Update Players
for p := range g.players {
living_robots := 0
for _, r := range p.Robots {
if r.Health > 0 {
living_robots++
r.Tick(g)
}
if len(r.Message) > 0 {
if len(r.Message) > 100 {
r.Message = r.Message[0:99]
}
payload.Messages = append(payload.Messages, r.Message)
}
payload.OtherRobots = append(
payload.OtherRobots,
r.GetTruncatedDetails())
payload.AllBots = append(
payload.AllBots,
BotHealth{RobotId: r.Id, Health: r.Health})
}
if living_robots > 0 {
g.players_remaining++
}
}
// 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)
}
}
func (g *game) sendUpdate(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.AllBots = payload.AllBots
player_payload.Turn = payload.Turn
for _, r := range p.Robots {
player_payload.MyRobots = append(player_payload.MyRobots, *r)
// player_payload.OtherRobots = append(
// player_payload.OtherRobots,
// r.GetTruncatedDetails())
}
player_payload.Objects = [][4]int{}
player_payload.Splosions = []Splosion{}
player_payload.Projectiles = []Projectile{}
living_robots := 0
for _, r := range p.Robots {
if r.Health > 0 {
living_robots++
// Filter robots by scanner
for player := range g.players {
for _, scan_entry := range r.Scanners {
for _, r := range player.Robots {
if r.Id == scan_entry.Id {
player_payload.OtherRobots = append(
player_payload.OtherRobots,
r.GetTruncatedDetails())
}
}
}
}
// Filter projectiles
for proj := range g.projectiles {
if proj.Owner == r {
player_payload.Projectiles = append(
player_payload.Projectiles,
*proj)
}
for _, scan_entry := range r.Scanners {
if proj.Id == scan_entry.Id {
player_payload.Projectiles = append(
player_payload.Projectiles,
*proj)
}
}
}
// Filter splosions
for splo := range g.splosions {
for _, scan_entry := range r.Scanners {
if splo.Id == scan_entry.Id {
player_payload.Splosions = append(
player_payload.Splosions,
*splo)
}
}
}
// Filter objects
for _, ob := range g.obstacles {
if ob.distance_from_point(r.Position) < float32(r.Stats.ScannerRadius)+r.ScanCounter {
player_payload.Objects = append(
player_payload.Objects, ob.minify())
}
}
}
}
// if living_robots == 0 {
// player_payload.OtherRobots = payload.OtherRobots
// player_payload.Projectiles = payload.Projectiles
// player_payload.Splosions = payload.Splosions
// player_payload.Objects = payload.Objects
// }
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(conf.Tick) * time.Millisecond)
for {
select {
case <-g.kill:
log.Printf("game %s: received kill signal, dying gracefully", g.id)
close(g.bw.Quit)
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
g.stats.PlayerStats[p.Id] = &PlayerStats{
BotStats: make(map[string]*BotStats),
}
for _, r := range p.Robots {
g.stats.PlayerStats[p.Id].BotStats[r.Name] = &BotStats{}
r.gameStats = g.stats.PlayerStats[p.Id].BotStats[r.Name]
}
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
if end, data := g.mode.gameOver(g); end {
g.sendGameOver(data)
}
g.tick(payload)
g.mode.tick(g, payload)
t1 = time.Now()
if *verbose {
log.Printf("Turn Processes %v\n", t1.Sub(t0))
}
// SEND THE UPDATE TO EACH PLAYER
g.sendUpdate(payload)
t1 = time.Now()
if *verbose {
log.Printf("Sent Payload %v\n", t1.Sub(t0))
}
}
}
}
func (g *game) sendGameOver(eg *GameOver) {
log.Printf("sending out game over message: %+v", eg)
for p := range g.players {
p.send <- eg
}
for s := range g.spectators {
s.send <- eg
}
}
// returns a GameParam object popuplated by info from the game. This is
// used during client/server initial negociation.
func (g *game) gameParam() *GameParam {
return &GameParam{
BoardSize: BoardSize{
Width: g.width,
Height: g.height,
},
MaxPoints: g.maxPoints,
Type: "gameparam",
}
}