diff --git a/cmd/rove/main.go b/cmd/rove/main.go index 2fc436a..a727346 100644 --- a/cmd/rove/main.go +++ b/cmd/rove/main.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "io/ioutil" + "math" "os" "path" @@ -173,9 +174,14 @@ func InnerMain(command string) error { return fmt.Errorf("Server returned failure: %s", response.Error) } else { - fmt.Printf("radar blips: %+v\n", response.Blips) - - // TODO: Do some art + // Print the radar + num := int(math.Sqrt(float64(len(response.Tiles)))) + for i := 0; i < num; i++ { + for j := num - 1; j >= 0; j-- { + fmt.Printf("%d", response.Tiles[i+num*j]) + } + fmt.Print("\n") + } } case "rover": diff --git a/pkg/game/atlas.go b/pkg/game/atlas.go index ac0b9c5..9d6b166 100644 --- a/pkg/game/atlas.go +++ b/pkg/game/atlas.go @@ -144,9 +144,17 @@ func (a *Atlas) ChunkOrigin(chunk int) Vector { return v.Multiplied(a.ChunkSize) } -// GetWorldExtent gets the extent of the world -func (a *Atlas) GetWorldExtent() int { - return (a.Size / 2) * a.ChunkSize +// GetWorldExtent gets the min and max valid coordinates of world +func (a *Atlas) GetWorldExtents() (min Vector, max Vector) { + min = Vector{ + -(a.Size / 2) * a.ChunkSize, + -(a.Size / 2) * a.ChunkSize, + } + max = Vector{ + -min.X - 1, + -min.Y - 1, + } + return } // Grow will return a grown copy of the current atlas diff --git a/pkg/game/world.go b/pkg/game/world.go index 8bd34e6..454f1ba 100644 --- a/pkg/game/world.go +++ b/pkg/game/world.go @@ -128,6 +128,7 @@ func (w *World) WarpRover(id uuid.UUID, pos Vector) error { if i, ok := w.Rovers[id]; ok { // Update the world tile // TODO: Make this (and other things) transactional + // TODO: Check this worldtile is free if err := w.Atlas.SetTile(pos, TileRover); err != nil { return fmt.Errorf("coudln't set rover tile: %s", err) } else if err := w.Atlas.SetTile(i.Attributes.Pos, TileEmpty); err != nil { @@ -180,52 +181,55 @@ func (w *World) MoveRover(id uuid.UUID, bearing Direction) (RoverAttributes, err } } -// RadarBlip represents a single blip on the radar -type RadarBlip struct { - Position Vector `json:"position"` - Tile Tile `json:"tile"` -} - // RadarFromRover can be used to query what a rover can currently see -func (w *World) RadarFromRover(id uuid.UUID) ([]RadarBlip, error) { +func (w *World) RadarFromRover(id uuid.UUID) ([]Tile, error) { w.worldMutex.RLock() defer w.worldMutex.RUnlock() if r, ok := w.Rovers[id]; ok { - var blips []RadarBlip + // The radar should span in range direction on each axis, plus the row/column the rover is currently on + radarSpan := (r.Attributes.Range * 2) + 1 + roverPos := r.Attributes.Pos - extent := w.Atlas.GetWorldExtent() - - // Get min and max extents to query - min := Vector{ - Max(-extent, r.Attributes.Pos.X-r.Attributes.Range), - Max(-extent, r.Attributes.Pos.Y-r.Attributes.Range), + // Get the radar min and max values + radarMin := Vector{ + X: roverPos.X - r.Attributes.Range, + Y: roverPos.Y - r.Attributes.Range, } - max := Vector{ - Min(extent-1, r.Attributes.Pos.X+r.Attributes.Range), - Min(extent-1, r.Attributes.Pos.Y+r.Attributes.Range), + radarMax := Vector{ + X: roverPos.X + r.Attributes.Range, + Y: roverPos.Y + r.Attributes.Range, + } + + // Make sure we only query within the actual world + worldMin, worldMax := w.Atlas.GetWorldExtents() + scanMin := Vector{ + X: Max(radarMin.X, worldMin.X), + Y: Max(radarMin.Y, worldMin.Y), + } + scanMax := Vector{ + X: Min(radarMax.X, worldMax.X), + Y: Min(radarMax.Y, worldMax.Y), } // Gather up all tiles within the range - for i := min.X; i < max.X; i++ { - for j := min.Y; j < max.Y; j++ { - - // Skip this rover + var radar = make([]Tile, radarSpan*radarSpan) + for i := scanMin.X; i < scanMax.X; i++ { + for j := scanMin.Y; j < scanMax.Y; j++ { q := Vector{i, j} - if q == r.Attributes.Pos { - continue - } if tile, err := w.Atlas.GetTile(q); err != nil { - return blips, fmt.Errorf("failed to query tile: %s", err) + return nil, fmt.Errorf("failed to query tile: %s", err) - } else if tile != TileEmpty { - blips = append(blips, RadarBlip{Position: q, Tile: tile}) + } else { + // Get the position relative to the bottom left of the radar + relative := q.Added(radarMin.Negated()) + radar[relative.X+relative.Y*radarSpan] = tile } } } - return blips, nil + return radar, nil } else { return nil, fmt.Errorf("no rover matching id") } diff --git a/pkg/game/world_test.go b/pkg/game/world_test.go index dc39a24..f1d798e 100644 --- a/pkg/game/world_test.go +++ b/pkg/game/world_test.go @@ -96,30 +96,24 @@ func TestWorld_RadarFromRover(t *testing.T) { assert.NoError(t, err) b, err := world.SpawnRover() assert.NoError(t, err) - c, err := world.SpawnRover() - assert.NoError(t, err) // Get a's attributes attrib, err := world.RoverAttributes(a) assert.NoError(t, err, "Failed to get rover attribs") - // Warp the rovers so a can see b but not c - bpos := Vector{attrib.Range - 1, 0} - cpos := Vector{attrib.Range + 1, 0} + // Warp the rovers so a can see b + bpos := Vector{-attrib.Range, -attrib.Range} assert.NoError(t, world.WarpRover(a, Vector{0, 0}), "Failed to warp rover") assert.NoError(t, world.WarpRover(b, bpos), "Failed to warp rover") - assert.NoError(t, world.WarpRover(c, cpos), "Failed to warp rover") radar, err := world.RadarFromRover(a) assert.NoError(t, err, "Failed to get radar from rover") - assert.Equal(t, 1, len(radar), "Radar returned wrong number of rovers") + fullRange := attrib.Range + attrib.Range + 1 + assert.Equal(t, fullRange*fullRange, len(radar), "Radar returned wrong number of rovers") - found := false - for _, blip := range radar { - if blip.Position == bpos && blip.Tile == TileRover { - found = true - } - } - assert.True(t, found, "Rover not found on radar in expected position") + // bottom left should be a rover (we put one there with bpos) + assert.Equal(t, radar[0], TileRover, "Rover not found on radar in expected position") + // Centre should be rover + assert.Equal(t, radar[fullRange*fullRange/2], TileRover, "Rover not found on radar in expected position") } diff --git a/pkg/rove/api.go b/pkg/rove/api.go index b878111..45c6d50 100644 --- a/pkg/rove/api.go +++ b/pkg/rove/api.go @@ -107,7 +107,8 @@ type RadarResponse struct { Error string `json:"error,omitempty"` // The set of positions for nearby non-empty tiles - Blips []game.RadarBlip `json:"blips"` + Range int `json:"range"` + Tiles []game.Tile `json:"tiles"` } // ================ diff --git a/pkg/server/routes.go b/pkg/server/routes.go index 29a2bfd..48f8d1d 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -97,12 +97,11 @@ func HandleRegister(s *Server, vars map[string]string, b io.ReadCloser, w io.Wri } else { // Save out the new accounts - fmt.Printf("New account registered\tname:%s\tid:%s\n", acc.Name, acc.Id) - response.Id = acc.Id.String() response.Success = true } + fmt.Printf("register response:%+v\n", response) return response, nil } @@ -126,19 +125,18 @@ func HandleSpawn(s *Server, vars map[string]string, b io.ReadCloser, w io.Writer } else if id, err := uuid.Parse(id); err != nil { response.Error = "Provided account ID was invalid" - } else if attribs, rover, err := s.SpawnRoverForAccount(id); err != nil { + } else if attribs, _, err := s.SpawnRoverForAccount(id); err != nil { response.Error = err.Error() } else if err := s.SaveWorld(); err != nil { response.Error = fmt.Sprintf("Internal server error when saving world: %s", err) } else { - fmt.Printf("New rover spawned\taccount:%s\trover:%s\tattributes:%+v\n", id, rover, attribs) - response.Success = true response.Attributes = attribs } + fmt.Printf("spawn response \taccount:%s\tresponse:%+v\n", id, response) return response, nil } @@ -169,10 +167,10 @@ func HandleCommand(s *Server, vars map[string]string, b io.ReadCloser, w io.Writ response.Error = fmt.Sprintf("Failed to execute commands: %s", err) } else { - fmt.Printf("Queued commands\taccount:%s\tcommands:%+v\n", id, data.Commands) response.Success = true } + fmt.Printf("command response \taccount:%s\tresponse:%+v\n", id, response) return response, nil } @@ -192,15 +190,19 @@ func HandleRadar(s *Server, vars map[string]string, b io.ReadCloser, w io.Writer } else if inst, err := s.accountant.GetRover(id); err != nil { response.Error = fmt.Sprintf("Provided account has no rover: %s", err) + } else if attrib, err := s.world.RoverAttributes(inst); err != nil { + response.Error = fmt.Sprintf("Error getting rover attributes: %s", err) + } else if radar, err := s.world.RadarFromRover(inst); err != nil { response.Error = fmt.Sprintf("Error getting radar from rover: %s", err) } else { - fmt.Printf("Responded with radar\taccount:%s\tradar:%+v\n", id, radar) - response.Blips = radar + response.Tiles = radar + response.Range = attrib.Range response.Success = true } + fmt.Printf("radar response \taccount:%s\tresponse:%+v\n", id, response) return response, nil } @@ -224,10 +226,10 @@ func HandleRover(s *Server, vars map[string]string, b io.ReadCloser, w io.Writer response.Error = fmt.Sprintf("Error getting radar from rover: %s", err) } else { - fmt.Printf("Responded with rover\taccount:%s\trover:%+v\n", id, attribs) response.Attributes = attribs response.Success = true } + fmt.Printf("rover response \taccount:%s\tresponse:%+v\n", id, response) return response, nil } diff --git a/pkg/server/routes_test.go b/pkg/server/routes_test.go index 4fa85fd..db3e171 100644 --- a/pkg/server/routes_test.go +++ b/pkg/server/routes_test.go @@ -146,13 +146,6 @@ func TestHandleRadar(t *testing.T) { // Warp this rover to 0 assert.NoError(t, s.world.WarpRover(id, game.Vector{})) - // Spawn another rover - id, err = s.world.SpawnRover() - assert.NoError(t, err) - // Warp this rover to just above the other one - roverPos := game.Vector{X: 0, Y: 1} - assert.NoError(t, s.world.WarpRover(id, roverPos)) - // Set a tile to wall below this rover wallPos := game.Vector{X: 0, Y: -1} assert.NoError(t, s.world.Atlas.SetTile(wallPos, game.TileWall)) @@ -170,18 +163,7 @@ func TestHandleRadar(t *testing.T) { t.Errorf("got false for /radar: %s", status.Error) } - foundWall := false - foundRover := false - for _, b := range status.Blips { - if b.Position == wallPos && b.Tile == game.TileWall { - foundWall = true - } else if b.Position == roverPos && b.Tile == game.TileRover { - foundRover = true - } - } - assert.True(t, foundWall) - assert.True(t, foundRover) - + assert.Equal(t, game.TileRover, status.Tiles[len(status.Tiles)/2]) } func TestHandleRover(t *testing.T) {