2013-08-19 20:43:26 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"log"
|
|
|
|
"sort"
|
2013-10-18 20:48:22 -07:00
|
|
|
"sync"
|
2013-08-19 20:43:26 -07:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2013-09-08 09:32:24 -07:00
|
|
|
const maxPlayer = 128
|
|
|
|
|
2013-10-24 06:26:54 -07:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2013-10-18 20:48:22 -07:00
|
|
|
type MapLock struct {
|
|
|
|
m map[string]*game
|
|
|
|
sync.RWMutex
|
|
|
|
}
|
|
|
|
|
2013-10-18 23:55:52 -07:00
|
|
|
// 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.
|
2013-11-06 21:12:10 -08:00
|
|
|
func (ml *MapLock) get(id string) *game {
|
2013-10-18 20:48:22 -07:00
|
|
|
ml.Lock()
|
2013-11-06 21:12:10 -08:00
|
|
|
g, _ := ml.m[id]
|
2013-10-18 20:48:22 -07:00
|
|
|
ml.Unlock()
|
2013-11-06 21:12:10 -08:00
|
|
|
return g
|
|
|
|
}
|
2013-10-18 20:48:22 -07:00
|
|
|
|
2013-11-06 21:12:10 -08:00
|
|
|
func (ml *MapLock) add(g *game) {
|
2013-10-18 20:48:22 -07:00
|
|
|
ml.Lock()
|
2013-11-06 21:12:10 -08:00
|
|
|
ml.m[g.id] = g
|
2013-10-18 20:48:22 -07:00
|
|
|
ml.Unlock()
|
|
|
|
}
|
|
|
|
|
2013-11-13 23:45:02 -08:00
|
|
|
type WinnerMap struct {
|
|
|
|
m map[string]int
|
|
|
|
sync.RWMutex
|
|
|
|
}
|
|
|
|
|
2013-08-19 20:43:26 -07:00
|
|
|
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
|
|
|
|
register chan *player
|
|
|
|
unregister chan *player
|
|
|
|
turn int
|
|
|
|
players_remaining int
|
|
|
|
width, height float32
|
|
|
|
spectators map[*Spectator]bool
|
|
|
|
sregister chan *Spectator
|
|
|
|
sunregister chan *Spectator
|
|
|
|
kill chan bool
|
|
|
|
repair_hp int
|
|
|
|
repair_rate float32
|
|
|
|
tick_duration int
|
2013-11-13 23:45:02 -08:00
|
|
|
winners WinnerMap
|
2013-08-19 20:43:26 -07:00
|
|
|
}
|
|
|
|
|
2013-11-13 20:38:57 -08:00
|
|
|
func NewGame(id string, width, height float32, tick int) *game {
|
2013-09-04 00:06:39 -07:00
|
|
|
g := &game{
|
2013-11-13 23:45:02 -08:00
|
|
|
id: id,
|
|
|
|
register: make(chan *player),
|
|
|
|
unregister: make(chan *player, maxPlayer),
|
|
|
|
projectiles: make(map[*Projectile]bool),
|
|
|
|
splosions: make(map[*Splosion]bool),
|
|
|
|
obstacles: GenerateObstacles(conf.Obstacles, 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,
|
|
|
|
tick_duration: tick,
|
|
|
|
players_remaining: 2,
|
|
|
|
winners: WinnerMap{m: make(map[string]int)},
|
2013-08-25 23:10:02 -07:00
|
|
|
}
|
2013-09-04 00:06:39 -07:00
|
|
|
return g
|
|
|
|
}
|
|
|
|
|
2013-11-13 22:24:54 -08:00
|
|
|
func (g *game) tick(payload *Boardstate) {
|
|
|
|
g.players_remaining = 0
|
2013-10-25 22:30:15 -07:00
|
|
|
payload.Obstacles = g.obstacles
|
|
|
|
|
2013-10-20 21:15:23 -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-10-24 06:26:54 -07:00
|
|
|
|
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
|
|
|
}
|
2013-10-20 21:15:23 -07: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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-09 22:05:37 -08:00
|
|
|
func (g *game) sendUpdate(payload *Boardstate) {
|
2013-10-20 21:15:23 -07:00
|
|
|
// Ensure that the robots are always sent in a consistent order
|
2013-10-24 20:13:21 -07:00
|
|
|
sort.Sort(RobotSorter{Robots: payload.OtherRobots})
|
2013-11-06 21:12:19 -08:00
|
|
|
sort.Sort(AllRobotSorter{Robots: payload.AllBots})
|
2013-10-20 21:15:23 -07:00
|
|
|
|
|
|
|
for p := range g.players {
|
2013-11-08 21:25:42 -08:00
|
|
|
|
2013-10-20 21:15:23 -07: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
|
2013-10-20 21:15:23 -07:00
|
|
|
player_payload.Splosions = payload.Splosions
|
2013-10-25 22:30:15 -07:00
|
|
|
player_payload.Obstacles = payload.Obstacles
|
2013-10-24 06:26:54 -07:00
|
|
|
player_payload.AllBots = payload.AllBots
|
2013-10-20 21:15:23 -07:00
|
|
|
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)
|
2013-11-08 21:25:42 -08:00
|
|
|
player_payload.OtherRobots = append(
|
|
|
|
player_payload.OtherRobots,
|
|
|
|
r.GetTruncatedDetails())
|
|
|
|
}
|
2013-10-20 21:15:23 -07:00
|
|
|
|
2013-11-06 22:21:04 -08:00
|
|
|
player_payload.Projectiles = []Projectile{}
|
|
|
|
player_payload.Obstacles = []Obstacle{}
|
2013-11-08 21:25:42 -08:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
}
|
2013-10-20 21:15:23 -07:00
|
|
|
}
|
|
|
|
}
|
2013-11-06 22:21:04 -08:00
|
|
|
|
2013-11-08 21:25:42 -08:00
|
|
|
// Filter projectiles
|
|
|
|
for proj := range g.projectiles {
|
2013-11-06 22:21:04 -08:00
|
|
|
|
2013-11-08 22:26:56 -08:00
|
|
|
if proj.Owner == r {
|
2013-11-06 22:21:04 -08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2013-11-06 22:21:04 -08:00
|
|
|
}
|
|
|
|
|
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.Obstacles = append(
|
|
|
|
player_payload.Obstacles,
|
|
|
|
ob)
|
|
|
|
}
|
2013-11-06 22:21:04 -08:00
|
|
|
}
|
|
|
|
}
|
2013-11-08 21:25:42 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if living_robots == 0 {
|
2013-10-24 20:13:21 -07:00
|
|
|
player_payload.OtherRobots = payload.OtherRobots
|
2013-11-06 22:21:04 -08:00
|
|
|
player_payload.Projectiles = payload.Projectiles
|
|
|
|
player_payload.Obstacles = payload.Obstacles
|
2013-10-20 21:15:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
p.send <- player_payload
|
|
|
|
}
|
|
|
|
for s := range g.spectators {
|
|
|
|
s.send <- payload
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-09-04 00:06:39 -07:00
|
|
|
func (g *game) run() {
|
2013-09-05 23:31:13 -07:00
|
|
|
var t0, t1 time.Time
|
2013-11-13 20:38:57 -08:00
|
|
|
ticker := time.NewTicker(time.Duration(conf.Tick) * time.Millisecond)
|
2013-08-19 20:43:26 -07:00
|
|
|
for {
|
|
|
|
select {
|
2013-08-28 22:16:55 -07:00
|
|
|
case <-g.kill:
|
2013-09-04 23:23:05 -07:00
|
|
|
log.Printf("game %s: received kill signal, dying gracefully", g.id)
|
2013-09-05 18:14:41 -07:00
|
|
|
games.Lock()
|
2013-09-08 09:32:24 -07:00
|
|
|
for player := range g.players {
|
|
|
|
close(player.send)
|
|
|
|
}
|
2013-09-05 18:14:41 -07:00
|
|
|
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:
|
2013-10-20 21:15:23 -07:00
|
|
|
t0 = time.Now()
|
2013-10-19 17:29:40 -07:00
|
|
|
payload := NewBoardstate()
|
2013-09-04 23:23:05 -07:00
|
|
|
|
2013-08-19 20:43:26 -07:00
|
|
|
g.turn++
|
2013-09-07 19:13:12 -07:00
|
|
|
payload.Turn = g.turn
|
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-10-19 17:29:40 -07:00
|
|
|
// UPDATE GAME STATE
|
2013-11-13 22:24:54 -08:00
|
|
|
if end, data := g.gameOver(); end {
|
|
|
|
g.sendGameOver(data)
|
2013-08-19 20:43:26 -07:00
|
|
|
}
|
|
|
|
|
2013-11-13 22:24:54 -08:00
|
|
|
g.tick(payload)
|
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2013-10-19 17:29:40 -07:00
|
|
|
// SEND THE UPDATE TO EACH PLAYER
|
2013-11-09 22:05:37 -08:00
|
|
|
g.sendUpdate(payload)
|
2013-08-19 20:43:26 -07:00
|
|
|
|
|
|
|
t1 = time.Now()
|
|
|
|
if *verbose {
|
|
|
|
log.Printf("Sent Payload %v\n", t1.Sub(t0))
|
|
|
|
}
|
2013-09-20 11:15:10 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-11-13 22:24:54 -08:00
|
|
|
|
|
|
|
func (g *game) gameOver() (bool, *GameOver) {
|
|
|
|
over := false
|
2013-11-13 23:45:02 -08:00
|
|
|
var stats *GameOver
|
2013-11-13 22:24:54 -08:00
|
|
|
|
|
|
|
if g.players_remaining <= 1 && len(g.players) > 1 {
|
|
|
|
g.obstacles = GenerateObstacles(conf.Obstacles, g.width, g.height)
|
|
|
|
log.Printf("game %s: game over", g.id)
|
2013-11-13 23:45:02 -08:00
|
|
|
stats = NewGameOver()
|
2013-11-13 22:24:54 -08:00
|
|
|
|
|
|
|
for p := range g.players {
|
|
|
|
for _, r := range p.Robots {
|
|
|
|
if r.Health > 0 {
|
|
|
|
log.Printf("Robot %v Survived", r.Id)
|
2013-11-13 23:45:02 -08:00
|
|
|
g.winners.Lock()
|
|
|
|
g.winners.m[r.Id] += 1
|
|
|
|
g.winners.Unlock()
|
|
|
|
stats.Winners = append(stats.Winners, r.Id)
|
2013-11-13 22:24:54 -08:00
|
|
|
}
|
|
|
|
r.reset(g)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
over = true
|
|
|
|
}
|
|
|
|
return over, stats
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|