diff --git a/commands.go b/commands.go index 4e21857..e1e5e66 100644 --- a/commands.go +++ b/commands.go @@ -211,15 +211,11 @@ func BroadcastCommand(sys *System) Command { func NearbyCommand(sys *System) Command { handler := func(c *Connection, args ...string) { - neighbors, err := sys.Nearby(25) - if err != nil { - log_error("unable to get neighbors: %v", err) - return - } + neighbors := c.game.galaxy.Neighborhood(sys) c.Printf("--------------------------------------------------------------------------------\n") c.Printf("%-4s %-20s %-12s %s\n", "id", "name", "distance", "trip time") c.Printf("--------------------------------------------------------------------------------\n") - for _, neighbor := range neighbors { + for _, neighbor := range neighbors[:25] { other := index[neighbor.id] dur := NewTravel(c, sys, other).(*TravelState).tripTime() c.Printf("%-4d %-20s %-12.6v %v\n", other.id, other.name, neighbor.distance, dur) diff --git a/connection.go b/connection.go index ec94920..2dfdec2 100644 --- a/connection.go +++ b/connection.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net" + "runtime" "sort" "strings" "time" @@ -52,9 +53,22 @@ func (c *Connection) Tick(frame int64) { func (c *Connection) RunCommand(name string, args ...string) { defer func() { if r := recover(); r != nil { - c.Printf("something is broken. Log this as a ticket!\n") - c.Printf("recovered: %v\n", r) + c.Printf("(something is broken)") + c.Printf("ERROR: %v\n", r) + callers := make([]uintptr, 40) + n := runtime.Callers(5, callers) + callers = callers[:n] + frames := runtime.CallersFrames(callers) log_error("recovered: %v", r) + for { + frame, more := frames.Next() + + if !more { + break + } + + log_error(" %s +%d (%s)\n", frame.File, frame.Line, frame.Function) + } } }() switch name { diff --git a/galaxy.go b/galaxy.go new file mode 100644 index 0000000..3d1c5bf --- /dev/null +++ b/galaxy.go @@ -0,0 +1,77 @@ +package main + +import ( + "math/rand" + "sort" + "strconv" +) + +// Galaxy is a collection of systems +type Galaxy struct { + systems map[int]*System + names map[string]int +} + +func NewGalaxy() *Galaxy { + g := &Galaxy{ + systems: make(map[int]*System), + names: make(map[string]int), + } + g.indexSystems() + return g +} + +func (g *Galaxy) indexSystems() { + rows, err := db.Query(`select * from planets`) + if err != nil { + log_error("unable to select all planets: %v", err) + return + } + defer rows.Close() + + for rows.Next() { + s := System{} + if err := rows.Scan(&s.id, &s.name, &s.x, &s.y, &s.z, &s.planets); err != nil { + log_info("unable to scan planet row: %v", err) + continue + } + g.systems[s.id] = &s + g.names[s.name] = s.id + s.money = int64(rand.NormFloat64()*options.moneySigma + options.moneyMean) + } +} + +// GetSystem gets a system by either ID or name. If the provided string +// contains an integer, we assume the lookup is intended to be by ID. +func (g *Galaxy) GetSystem(s string) *System { + id, err := strconv.Atoi(s) + if err == nil { + return g.GetSystemByID(id) + } + + return g.GetSystemByName(s) +} + +func (g *Galaxy) GetSystemByID(id int) *System { + return g.systems[id] +} + +func (g *Galaxy) GetSystemByName(name string) *System { + id := g.SystemID(name) + if id == 0 { + return nil + } + return g.GetSystemByID(id) +} + +func (g *Galaxy) SystemID(name string) int { return g.names[name] } + +// Neighborhood generates the neighborhood for a given system. +func (g *Galaxy) Neighborhood(sys *System) Neighborhood { + neighbors := make(Neighborhood, 0, len(g.systems)) + for id, sys2 := range g.systems { + neighbors = append(neighbors, Neighbor{id: id, distance: sys.DistanceTo(sys2)}) + } + sort.Sort(neighbors) + return neighbors +} diff --git a/game.go b/game.go index 72de927..4e5c699 100644 --- a/game.go +++ b/game.go @@ -16,6 +16,7 @@ type Game struct { connections map[*Connection]bool frame int64 elems map[GameElement]bool + galaxy *Galaxy } func gamesTable() { @@ -49,6 +50,7 @@ func NewGame() *Game { done: make(chan interface{}), connections: make(map[*Connection]bool, 32), elems: make(map[GameElement]bool, 32), + galaxy: NewGalaxy(), } if err := game.Create(); err != nil { log_error("unable to create game: %v", err) diff --git a/idle.go b/idle.go index 76f9fcd..fbb3c75 100644 --- a/idle.go +++ b/idle.go @@ -65,9 +65,9 @@ func (i *IdleState) Tick(c *Connection, frame int64) ConnectionState { } func (i *IdleState) travelTo(c *Connection, args ...string) { - dest, err := GetSystem(args[0]) - if err != nil { - c.Printf("%v\n", err) + dest := c.game.galaxy.GetSystem(args[0]) + if dest == nil { + c.Printf("no such system: %s", args[0]) return } c.SetState(NewTravel(c, i.System, dest)) @@ -83,9 +83,9 @@ func (i *IdleState) bomb(c *Connection, args ...string) { return } - target, err := GetSystem(args[0]) - if err != nil { - c.Printf("Cannot send bomb: %v\n", err) + target := c.game.galaxy.GetSystem(args[0]) + if target == nil { + c.Printf("Cannot send bomb: no such system: %v\n", args[0]) return } diff --git a/system.go b/system.go index 1757f2f..57a7f26 100644 --- a/system.go +++ b/system.go @@ -5,7 +5,6 @@ import ( "fmt" "math" "math/rand" - "strconv" "time" ) @@ -26,22 +25,6 @@ type System struct { money int64 } -func GetSystem(id string) (*System, error) { - idNum, err := strconv.Atoi(id) - if err == nil { - sys, ok := index[idNum] - if !ok { - return nil, fmt.Errorf("No such system: %v", idNum) - } - return sys, nil - } - sys, ok := nameIndex[id] - if !ok { - return nil, fmt.Errorf("No such system: %v", id) - } - return sys, nil -} - func (s *System) Tick(frame int64) { if s.colonizedBy != nil && s.money > 0 { s.colonizedBy.Deposit(1) @@ -215,36 +198,17 @@ func (s System) String() string { return fmt.Sprintf("%s (id: %v)", s.name, s.id) } +type Neighborhood []Neighbor + +func (n Neighborhood) Len() int { return len(n) } +func (n Neighborhood) Less(i, j int) bool { return n[i].distance < n[j].distance } +func (n Neighborhood) Swap(i, j int) { n[i], n[j] = n[j], n[i] } + type Neighbor struct { id int distance float64 } -func (e *System) Nearby(n int) ([]Neighbor, error) { - rows, err := db.Query(` - select planets.id, edges.distance - from edges - join planets on edges.id_2 = planets.id - where edges.id_1 = ? - order by distance - limit ? - ;`, e.id, n) - if err != nil { - log_error("unable to get nearby systems for %s: %v", e.name, err) - return nil, err - } - neighbors := make([]Neighbor, 0, n) - for rows.Next() { - var neighbor Neighbor - if err := rows.Scan(&neighbor.id, &neighbor.distance); err != nil { - log_error("error unpacking row from nearby neighbors query: %v", err) - continue - } - neighbors = append(neighbors, neighbor) - } - return neighbors, nil -} - func countSystems() (int, error) { row := db.QueryRow(`select count(*) from planets`)