server/game.go

349 lines
7.9 KiB
Go
Raw Normal View History

package botserv
2013-08-19 20:43:26 -07:00
// delete me
2013-08-19 20:43:26 -07:00
import (
"log"
"sort"
"sync"
2013-08-19 20:43:26 -07:00
"time"
"bitbucket.org/smcquay/bandwidth"
2013-08-19 20:43:26 -07:00
)
const maxPlayer = 128
type BotHealth struct {
RobotId string `json:"robot_id"`
Health int `json:"health"`
}
2013-09-27 22:27:05 -07:00
type Scanner struct {
2013-11-06 20:09:45 -08:00
Id string `json:"id"`
Type string `json:"type"`
2013-09-27 22:27:05 -07:00
}
2014-01-16 00:02:59 -08:00
type BotStats struct {
2014-01-16 00:13:02 -08:00
Kills int
Deaths int
Suicides int
Shots int
DirectHits int
Hits int
Wins int
2014-01-16 00:02:59 -08:00
}
type PlayerStats struct {
BotStats map[string]*BotStats
Wins int
}
type GameStats struct {
PlayerStats map[string]*PlayerStats
2013-11-13 23:45:02 -08:00
sync.RWMutex
}
type Game struct {
2013-11-13 22:24:54 -08:00
id string
players map[*player]bool
projectiles map[*Projectile]bool
splosions map[*Splosion]bool
obstacles []Obstacle
obstacle_count int
2013-11-13 22:24:54 -08:00
register chan *player
unregister chan *player
turn int
players_remaining int
width, height float32
2013-11-29 00:10:49 -08:00
maxPoints int
2013-11-13 22:24:54 -08:00
spectators map[*Spectator]bool
sregister chan *Spectator
sunregister chan *Spectator
kill chan bool
repair_hp int
repair_rate float32
tick_duration int
2014-01-16 00:02:59 -08:00
stats GameStats
2014-01-15 21:41:40 -08:00
mode GameMode
bw *bandwidth.Bandwidth
2014-01-15 21:41:40 -08:00
}
type GameMode interface {
setup(g *Game)
tick(gg *Game, payload *Boardstate)
gameOver(gg *Game) (bool, *GameOver)
2013-08-19 20:43:26 -07:00
}
func NewGame(id string, width, height float32, obstacles, tick, maxPoints int, mode string) (*Game, error) {
bw, err := bandwidth.NewBandwidth(
[]int{1, 10, 60},
2014-03-08 18:27:48 -08:00
1*time.Second,
)
if err != nil {
log.Fatal("seriously, what the fuck")
return nil, err
}
go bw.Run()
g := &Game{
2013-11-13 23:45:02 -08:00
id: id,
2014-01-29 22:12:26 -08:00
register: make(chan *player, maxPlayer),
2013-11-13 23:45:02 -08:00
unregister: make(chan *player, maxPlayer),
projectiles: make(map[*Projectile]bool),
splosions: make(map[*Splosion]bool),
2013-11-16 19:57:13 -08:00
obstacles: GenerateObstacles(obstacles, width, height),
obstacle_count: obstacles,
2013-11-13 23:45:02 -08:00
players: make(map[*player]bool),
turn: 0,
width: width,
height: height,
2013-11-29 00:10:49 -08:00
maxPoints: maxPoints,
2013-11-13 23:45:02 -08:00
spectators: make(map[*Spectator]bool),
sregister: make(chan *Spectator),
sunregister: make(chan *Spectator),
kill: make(chan bool),
2013-11-13 23:45:02 -08:00
repair_hp: 5,
repair_rate: 3.0,
tick_duration: tick,
players_remaining: 2,
2014-01-16 00:02:59 -08:00
stats: GameStats{PlayerStats: make(map[string]*PlayerStats)},
bw: bw,
}
if mode == "melee" {
g.mode = &melee{respawn: make(map[*Robot]float64)}
} else {
g.mode = &deathmatch{}
2013-08-25 23:10:02 -07:00
}
2014-01-15 21:41:40 -08:00
g.mode.setup(g)
return g, nil
2013-09-04 00:06:39 -07:00
}
func (g *Game) tick(payload *Boardstate) {
2013-11-13 22:24:54 -08:00
g.players_remaining = 0
payload.Objects = MinifyObstacles(g.obstacles)
2013-10-25 22:30:15 -07:00
// Update Players
for p := range g.players {
2013-11-08 21:25:42 -08:00
living_robots := 0
2013-11-07 22:10:18 -08:00
2013-11-08 21:25:42 -08:00
for _, r := range p.Robots {
if r.Health > 0 {
living_robots++
2013-11-08 22:26:56 -08:00
r.Tick(g)
2013-11-08 21:25:42 -08:00
}
if len(r.Message) > 0 {
if len(r.Message) > 100 {
r.Message = r.Message[0:99]
}
payload.Messages = append(payload.Messages, r.Message)
2013-11-07 22:10:18 -08:00
}
2013-11-08 21:25:42 -08:00
payload.OtherRobots = append(
payload.OtherRobots,
r.GetTruncatedDetails())
2013-11-08 21:25:42 -08:00
payload.AllBots = append(
payload.AllBots,
BotHealth{RobotId: r.Id, Health: r.Health})
}
if living_robots > 0 {
2013-11-13 22:24:54 -08:00
g.players_remaining++
2013-11-08 21:25:42 -08:00
}
}
// 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})
2013-11-06 21:12:19 -08:00
sort.Sort(AllRobotSorter{Robots: payload.AllBots})
for p := range g.players {
2013-11-08 21:25:42 -08:00
// Copy the payload but only add the robots in scanner range
player_payload := NewBoardstate()
2013-11-07 22:10:18 -08:00
player_payload.Messages = payload.Messages
player_payload.AllBots = payload.AllBots
player_payload.Turn = payload.Turn
2013-11-08 21:25:42 -08:00
for _, r := range p.Robots {
2013-11-08 22:26:56 -08:00
player_payload.MyRobots = append(player_payload.MyRobots, *r)
// player_payload.OtherRobots = append(
// player_payload.OtherRobots,
// r.GetTruncatedDetails())
2013-11-08 21:25:42 -08:00
}
player_payload.Objects = [][4]int{}
player_payload.Splosions = []Splosion{}
player_payload.Projectiles = []Projectile{}
2013-11-08 21:25:42 -08:00
living_robots := 0
for _, r := range p.Robots {
if r.Health > 0 {
living_robots++
2013-11-08 21:25:42 -08:00
// 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())
}
}
}
}
2013-11-08 21:25:42 -08:00
// Filter projectiles
for proj := range g.projectiles {
2013-11-08 22:26:56 -08:00
if proj.Owner == r {
player_payload.Projectiles = append(
player_payload.Projectiles,
*proj)
}
2013-11-08 21:25:42 -08:00
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)
}
}
}
2013-11-08 21:25:42 -08:00
// 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())
2013-11-08 21:25:42 -08:00
}
}
}
2013-11-08 21:25:42 -08:00
}
2013-11-18 22:48:55 -08:00
// 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() {
ticker := time.NewTicker(time.Duration(g.tick_duration) * 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)
g.bw.Quit <- true
for player := range g.players {
close(player.send)
}
2013-08-28 22:16:55 -07:00
return
2013-08-19 20:43:26 -07:00
case p := <-g.register:
log.Println("registering player:", p.Id)
2013-08-19 20:43:26 -07:00
g.players[p] = true
2014-01-16 00:02:59 -08:00
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]
}
2013-08-19 20:43:26 -07:00
case p := <-g.unregister:
log.Println("unregistering player:", p.Id)
2013-08-19 20:43:26 -07:00
delete(g.players, p)
close(p.send)
2013-08-19 22:23:35 -07:00
case s := <-g.sregister:
log.Println("registering spectator:", s.Id)
2013-08-19 22:23:35 -07:00
g.spectators[s] = true
case s := <-g.sunregister:
log.Println("unregistering spectator:", s.Id)
2013-08-19 22:23:35 -07:00
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-03 23:26:40 -07:00
// UPDATE GAME STATE
2014-01-15 21:41:40 -08:00
if end, data := g.mode.gameOver(g); end {
2013-11-13 22:24:54 -08:00
g.sendGameOver(data)
2013-08-19 20:43:26 -07:00
}
2013-11-13 22:24:54 -08:00
g.tick(payload)
2014-01-15 21:41:40 -08:00
g.mode.tick(g, payload)
2013-11-13 22:24:54 -08:00
// SEND THE UPDATE TO EACH PLAYER
g.sendUpdate(payload)
}
}
log.Println("run done")
}
2013-11-13 22:24:54 -08:00
func (g *Game) sendGameOver(eg *GameOver) {
2013-11-13 22:24:54 -08:00
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",
}
}