From 43588c0e4b8c193b41f07bed9bd2a649064d4ef0 Mon Sep 17 00:00:00 2001 From: Marc Di Luzio Date: Mon, 8 Jun 2020 23:02:09 +0100 Subject: [PATCH] Fix world spawning and radar Also expand test coverage a little to ensure it's correct --- cmd/rove/main.go | 6 ++--- pkg/game/atlas.go | 19 +++++++++----- pkg/game/atlas_test.go | 32 +++++++++++++++++------ pkg/game/command_test.go | 2 +- pkg/game/world.go | 30 +++++++++++++++++----- pkg/game/world_test.go | 53 ++++++++++++++++++++++++++++----------- pkg/server/routes_test.go | 31 +++++++++++++++++++---- pkg/server/server.go | 2 +- 8 files changed, 130 insertions(+), 45 deletions(-) diff --git a/cmd/rove/main.go b/cmd/rove/main.go index a727346..674428e 100644 --- a/cmd/rove/main.go +++ b/cmd/rove/main.go @@ -174,10 +174,10 @@ func InnerMain(command string) error { return fmt.Errorf("Server returned failure: %s", response.Error) } else { - // Print the radar + // Print out the radar num := int(math.Sqrt(float64(len(response.Tiles)))) - for i := 0; i < num; i++ { - for j := num - 1; j >= 0; j-- { + for j := num - 1; j >= 0; j-- { + for i := 0; i < num; i++ { fmt.Printf("%d", response.Tiles[i+num*j]) } fmt.Print("\n") diff --git a/pkg/game/atlas.go b/pkg/game/atlas.go index 9d6b166..be2b8fe 100644 --- a/pkg/game/atlas.go +++ b/pkg/game/atlas.go @@ -47,8 +47,8 @@ func NewAtlas(size int, chunkSize int) Atlas { return a } -// SpawnWorld spawns the current world -func (a *Atlas) SpawnWorld() error { +// SpawnRocks peppers the world with rocks +func (a *Atlas) SpawnRocks() error { extent := a.ChunkSize * (a.Size / 2) // Pepper the current world with rocks @@ -62,16 +62,23 @@ func (a *Atlas) SpawnWorld() error { } } + return nil +} + +// SpawnWalls spawns the around the world +func (a *Atlas) SpawnWalls() error { + extent := a.ChunkSize * (a.Size / 2) + // Surround the atlas in walls for i := -extent; i < extent; i++ { - if err := a.SetTile(Vector{i, extent - 1}, TileWall); err != nil { + if err := a.SetTile(Vector{i, extent - 1}, TileWall); err != nil { // N return err - } else if a.SetTile(Vector{extent - 1, i}, TileWall); err != nil { + } else if a.SetTile(Vector{extent - 1, i}, TileWall); err != nil { // E return err - } else if a.SetTile(Vector{-extent, i}, TileWall); err != nil { + } else if a.SetTile(Vector{i, -extent}, TileWall); err != nil { // S return err - } else if a.SetTile(Vector{i, extent - 1}, TileWall); err != nil { + } else if a.SetTile(Vector{-extent, i}, TileWall); err != nil { // W return err } } diff --git a/pkg/game/atlas_test.go b/pkg/game/atlas_test.go index f0090b1..7256061 100644 --- a/pkg/game/atlas_test.go +++ b/pkg/game/atlas_test.go @@ -137,16 +137,32 @@ func TestAtlas_Grown(t *testing.T) { func TestAtlas_SpawnWorld(t *testing.T) { // Start with a small example - a := NewAtlas(2, 2) + a := NewAtlas(2, 4) assert.NotNil(t, a) assert.Equal(t, 4, len(a.Chunks)) + assert.NoError(t, a.SpawnWalls()) - assert.NoError(t, a.SpawnWorld()) - tile, err := a.GetTile(Vector{1, 1}) - assert.NoError(t, err) - assert.Equal(t, TileWall, tile) + for i := -4; i < 4; i++ { + tile, err := a.GetTile(Vector{i, -4}) + assert.NoError(t, err) + assert.Equal(t, TileWall, tile) + } - tile, err = a.GetTile(Vector{-2, -2}) - assert.NoError(t, err) - assert.Equal(t, TileWall, tile) + for i := -4; i < 4; i++ { + tile, err := a.GetTile(Vector{-4, i}) + assert.NoError(t, err) + assert.Equal(t, TileWall, tile) + } + + for i := -4; i < 4; i++ { + tile, err := a.GetTile(Vector{3, i}) + assert.NoError(t, err) + assert.Equal(t, TileWall, tile) + } + + for i := -4; i < 4; i++ { + tile, err := a.GetTile(Vector{i, 3}) + assert.NoError(t, err) + assert.Equal(t, TileWall, tile) + } } diff --git a/pkg/game/command_test.go b/pkg/game/command_test.go index f221b66..1d823db 100644 --- a/pkg/game/command_test.go +++ b/pkg/game/command_test.go @@ -7,7 +7,7 @@ import ( ) func TestCommand_Move(t *testing.T) { - world := NewWorld() + world := NewWorld(2, 4) a, err := world.SpawnRover() assert.NoError(t, err) pos := Vector{ diff --git a/pkg/game/world.go b/pkg/game/world.go index 454f1ba..909a781 100644 --- a/pkg/game/world.go +++ b/pkg/game/world.go @@ -28,17 +28,20 @@ type World struct { } // NewWorld creates a new world object -func NewWorld() *World { +func NewWorld(size int, chunkSize int) *World { return &World{ Rovers: make(map[uuid.UUID]Rover), CommandQueue: make(map[uuid.UUID]CommandStream), - Atlas: NewAtlas(4, 8), // TODO: Choose an appropriate world size + Atlas: NewAtlas(size, chunkSize), // TODO: Choose an appropriate world size } } // SpawnWorld spawns a border at the edge of the world atlas func (w *World) SpawnWorld() error { - return w.Atlas.SpawnWorld() + if err := w.Atlas.SpawnRocks(); err != nil { + return err + } + return w.Atlas.SpawnWalls() } // SpawnRover adds an rover to the game @@ -120,6 +123,20 @@ func (w *World) RoverAttributes(id uuid.UUID) (RoverAttributes, error) { } } +// SetRoverAttributes sets the attributes of a requested rover +func (w *World) SetRoverAttributes(id uuid.UUID, attributes RoverAttributes) error { + w.worldMutex.Lock() + defer w.worldMutex.Unlock() + + if i, ok := w.Rovers[id]; ok { + i.Attributes = attributes + w.Rovers[id] = i + return nil + } else { + return fmt.Errorf("no rover matching id") + } +} + // WarpRover sets an rovers position func (w *World) WarpRover(id uuid.UUID, pos Vector) error { w.worldMutex.Lock() @@ -214,8 +231,8 @@ func (w *World) RadarFromRover(id uuid.UUID) ([]Tile, error) { // Gather up all tiles within the range var radar = make([]Tile, radarSpan*radarSpan) - for i := scanMin.X; i < scanMax.X; i++ { - for j := scanMin.Y; j < scanMax.Y; j++ { + for j := scanMin.Y; j <= scanMax.Y; j++ { + for i := scanMin.X; i <= scanMax.X; i++ { q := Vector{i, j} if tile, err := w.Atlas.GetTile(q); err != nil { @@ -224,7 +241,8 @@ func (w *World) RadarFromRover(id uuid.UUID) ([]Tile, error) { } else { // Get the position relative to the bottom left of the radar relative := q.Added(radarMin.Negated()) - radar[relative.X+relative.Y*radarSpan] = tile + index := relative.X + relative.Y*radarSpan + radar[index] = tile } } } diff --git a/pkg/game/world_test.go b/pkg/game/world_test.go index f1d798e..da19b40 100644 --- a/pkg/game/world_test.go +++ b/pkg/game/world_test.go @@ -8,14 +8,14 @@ import ( func TestNewWorld(t *testing.T) { // Very basic for now, nothing to verify - world := NewWorld() + world := NewWorld(4, 4) if world == nil { t.Error("Failed to create world") } } func TestWorld_CreateRover(t *testing.T) { - world := NewWorld() + world := NewWorld(2, 8) a, err := world.SpawnRover() assert.NoError(t, err) b, err := world.SpawnRover() @@ -30,7 +30,7 @@ func TestWorld_CreateRover(t *testing.T) { } func TestWorld_RoverAttributes(t *testing.T) { - world := NewWorld() + world := NewWorld(2, 4) a, err := world.SpawnRover() assert.NoError(t, err) @@ -41,7 +41,7 @@ func TestWorld_RoverAttributes(t *testing.T) { } func TestWorld_DestroyRover(t *testing.T) { - world := NewWorld() + world := NewWorld(4, 1) a, err := world.SpawnRover() assert.NoError(t, err) b, err := world.SpawnRover() @@ -59,7 +59,7 @@ func TestWorld_DestroyRover(t *testing.T) { } func TestWorld_GetSetMovePosition(t *testing.T) { - world := NewWorld() + world := NewWorld(4, 4) a, err := world.SpawnRover() assert.NoError(t, err) attribs, err := world.RoverAttributes(a) @@ -91,29 +91,52 @@ func TestWorld_GetSetMovePosition(t *testing.T) { } func TestWorld_RadarFromRover(t *testing.T) { - world := NewWorld() + // Create world that should have visible walls on the radar + world := NewWorld(4, 2) a, err := world.SpawnRover() assert.NoError(t, err) b, err := world.SpawnRover() assert.NoError(t, err) - // Get a's attributes + // Set the rover range to a predictable value attrib, err := world.RoverAttributes(a) assert.NoError(t, err, "Failed to get rover attribs") + attrib.Range = 4 // Set the range to 4 so we can predict the radar fully + err = world.SetRoverAttributes(a, attrib) + assert.NoError(t, err, "Failed to set rover attribs") - // Warp the rovers so a can see b - bpos := Vector{-attrib.Range, -attrib.Range} + // Warp the rovers into position + bpos := Vector{-3, -3} assert.NoError(t, world.WarpRover(a, Vector{0, 0}), "Failed to warp rover") assert.NoError(t, world.WarpRover(b, bpos), "Failed to warp rover") + // Spawn the world wall + err = world.Atlas.SpawnWalls() + assert.NoError(t, err) + radar, err := world.RadarFromRover(a) assert.NoError(t, err, "Failed to get radar from rover") - fullRange := attrib.Range + attrib.Range + 1 - assert.Equal(t, fullRange*fullRange, len(radar), "Radar returned wrong number of rovers") + fullRange := 4 + 4 + 1 + assert.Equal(t, fullRange*fullRange, len(radar), "Radar returned wrong length") - // 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") + // It should look like: + // --------- + // OOOOOOOO- + // O------O- + // O------O- + // O---R--O- + // O------O- + // O------O- + // OR-----O- + // OOOOOOOO- + // Test all expected values + assert.Equal(t, TileRover, radar[1+fullRange]) + assert.Equal(t, TileRover, radar[4+4*fullRange]) + for i := 0; i < 8; i++ { + assert.Equal(t, TileWall, radar[i]) + assert.Equal(t, TileWall, radar[i+(7*9)]) + assert.Equal(t, TileWall, radar[i*9]) + assert.Equal(t, TileWall, radar[(i*9)+7]) + } } diff --git a/pkg/server/routes_test.go b/pkg/server/routes_test.go index db3e171..b933ec0 100644 --- a/pkg/server/routes_test.go +++ b/pkg/server/routes_test.go @@ -140,15 +140,21 @@ func TestHandleRadar(t *testing.T) { assert.NoError(t, err, "Error registering account") // Spawn the rover rover for the account - _, id, err := s.SpawnRoverForAccount(a.Id) + attrib, id, err := s.SpawnRoverForAccount(a.Id) assert.NoError(t, err) - // Warp this rover to 0 + // Warp this rover to 0,0 assert.NoError(t, s.world.WarpRover(id, game.Vector{})) - // 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)) + // Explicity set a few nearby tiles + wallPos1 := game.Vector{X: 0, Y: -1} + wallPos2 := game.Vector{X: 1, Y: 1} + rockPos := game.Vector{X: 1, Y: 3} + emptyPos := game.Vector{X: -2, Y: -3} + assert.NoError(t, s.world.Atlas.SetTile(wallPos1, game.TileWall)) + assert.NoError(t, s.world.Atlas.SetTile(wallPos2, game.TileWall)) + assert.NoError(t, s.world.Atlas.SetTile(rockPos, game.TileRock)) + assert.NoError(t, s.world.Atlas.SetTile(emptyPos, game.TileEmpty)) request, _ := http.NewRequest(http.MethodGet, path.Join("/", a.Id.String(), "/radar"), nil) response := httptest.NewRecorder() @@ -163,7 +169,22 @@ func TestHandleRadar(t *testing.T) { t.Errorf("got false for /radar: %s", status.Error) } + scope := attrib.Range*2 + 1 + radarOrigin := game.Vector{X: -attrib.Range, Y: -attrib.Range} + + // Make sure the rover tile is correct assert.Equal(t, game.TileRover, status.Tiles[len(status.Tiles)/2]) + + // Check our other tiles + wallPos1.Add(radarOrigin.Negated()) + wallPos2.Add(radarOrigin.Negated()) + rockPos.Add(radarOrigin.Negated()) + emptyPos.Add(radarOrigin.Negated()) + assert.Equal(t, game.TileWall, status.Tiles[wallPos1.X+wallPos1.Y*scope]) + assert.Equal(t, game.TileWall, status.Tiles[wallPos2.X+wallPos2.Y*scope]) + assert.Equal(t, game.TileRock, status.Tiles[rockPos.X+rockPos.Y*scope]) + assert.Equal(t, game.TileEmpty, status.Tiles[emptyPos.X+emptyPos.Y*scope]) + } func TestHandleRover(t *testing.T) { diff --git a/pkg/server/server.go b/pkg/server/server.go index 9ad1c21..206d135 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -98,7 +98,7 @@ func NewServer(opts ...ServerOption) *Server { // Create the accountant s.accountant = accounts.NewAccountant() - s.world = game.NewWorld() + s.world = game.NewWorld(4, 8) // TODO: Configure this return s }