|
|
- 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},
- 1*time.Second,
- )
- 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",
- }
- }
|