diff --git a/cmd/rove-server/internal/server.go b/cmd/rove-server/internal/server.go index 0345936..de9d933 100644 --- a/cmd/rove-server/internal/server.go +++ b/cmd/rove-server/internal/server.go @@ -83,6 +83,7 @@ func NewServer(opts ...ServerOption) *Server { address: "", persistence: EphemeralData, schedule: cron.New(), + world: game.NewWorld(16), } // Apply all options @@ -90,9 +91,6 @@ func NewServer(opts ...ServerOption) *Server { o(s) } - // Start small, we can grow the world later - s.world = game.NewWorld(4, 8) - return s } @@ -114,11 +112,6 @@ func (s *Server) Initialise(fillWorld bool) (err error) { } s.accountant = accounts.NewAccountantClient(s.clientConn) - // Spawn a border on the default world - if err := s.world.SpawnWorld(fillWorld); err != nil { - return err - } - // Load the world file if err := s.LoadWorld(); err != nil { return err diff --git a/pkg/atlas/atlas.go b/pkg/atlas/atlas.go index 429efcb..969712f 100644 --- a/pkg/atlas/atlas.go +++ b/pkg/atlas/atlas.go @@ -1,7 +1,6 @@ package atlas import ( - "fmt" "log" "math/rand" @@ -16,114 +15,98 @@ type Chunk struct { Tiles []byte `json:"tiles"` } +// SpawnContent will create a chunk and fill it with spawned tiles +func (c *Chunk) SpawnContent(size int) { + c.Tiles = make([]byte, size*size) + for i := 0; i < len(c.Tiles); i++ { + c.Tiles[i] = objects.Empty + } + + // For now, fill it randomly with objects + for i := range c.Tiles { + if rand.Intn(16) == 0 { + c.Tiles[i] = objects.LargeRock + } else if rand.Intn(32) == 0 { + c.Tiles[i] = objects.SmallRock + } + } +} + // Atlas represents a grid of Chunks type Atlas struct { // Chunks represents all chunks in the world // This is intentionally not a 2D array so it can be expanded in all directions Chunks []Chunk `json:"chunks"` - // size is the current width/height of the given atlas - Size int `json:"size"` + // CurrentSize is the current width/height of the given atlas + CurrentSize int `json:"currentSize"` // ChunkSize is the dimensions of each chunk ChunkSize int `json:"chunksize"` } // NewAtlas creates a new empty atlas -func NewAtlas(size, chunkSize int) Atlas { - if size%2 != 0 { - log.Fatal("atlas size must always be even") +func NewAtlas(chunkSize int) Atlas { + return Atlas{ + CurrentSize: 0, + Chunks: nil, + ChunkSize: chunkSize, } - - a := Atlas{ - Size: size, - Chunks: make([]Chunk, size*size), - ChunkSize: chunkSize, - } - - // Initialise all the chunks - for i := range a.Chunks { - tiles := make([]byte, chunkSize*chunkSize) - for i := 0; i < len(tiles); i++ { - tiles[i] = objects.Empty - } - a.Chunks[i] = Chunk{ - Tiles: tiles, - } - } - - return a -} - -// SpawnRocks peppers the world with rocks -func (a *Atlas) SpawnRocks() error { - extent := a.ChunkSize * (a.Size / 2) - - // Pepper the current world with rocks - for i := -extent; i < extent; i++ { - for j := -extent; j < extent; j++ { - if rand.Intn(16) == 0 { - if err := a.SetTile(vector.Vector{X: i, Y: j}, objects.SmallRock); err != nil { - return err - } - } - } - } - - 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.Vector{X: i, Y: extent - 1}, objects.LargeRock); err != nil { // N - return err - } else if err := a.SetTile(vector.Vector{X: extent - 1, Y: i}, objects.LargeRock); err != nil { // E - return err - } else if err := a.SetTile(vector.Vector{X: i, Y: -extent}, objects.LargeRock); err != nil { // S - return err - } else if err := a.SetTile(vector.Vector{X: -extent, Y: i}, objects.LargeRock); err != nil { // W - return err - } - } - - return nil } // SetTile sets an individual tile's kind -func (a *Atlas) SetTile(v vector.Vector, tile byte) error { - chunk := a.toChunk(v) - if chunk >= len(a.Chunks) { - return fmt.Errorf("location outside of allocated atlas") +func (a *Atlas) SetTile(v vector.Vector, tile byte) { + // Get the chunk, expand, and spawn it if needed + c := a.toChunkWithGrow(v) + chunk := a.Chunks[c] + if chunk.Tiles == nil { + chunk.SpawnContent(a.ChunkSize) } local := a.toChunkLocal(v) tileId := local.X + local.Y*a.ChunkSize - if tileId >= len(a.Chunks[chunk].Tiles) { - return fmt.Errorf("location outside of allocated chunk") + + // Sanity check + if tileId >= len(chunk.Tiles) || tileId < 0 { + log.Fatalf("Local tileID is not in valid chunk, somehow, this means something is very wrong") } - a.Chunks[chunk].Tiles[tileId] = tile - return nil + + // Set the chunk back + chunk.Tiles[tileId] = tile + a.Chunks[c] = chunk } // GetTile will return an individual tile -func (a *Atlas) GetTile(v vector.Vector) (byte, error) { - chunk := a.toChunk(v) - if chunk >= len(a.Chunks) { - return 0, fmt.Errorf("location outside of allocated atlas") +func (a *Atlas) GetTile(v vector.Vector) byte { + // Get the chunk, expand, and spawn it if needed + c := a.toChunkWithGrow(v) + chunk := a.Chunks[c] + if chunk.Tiles == nil { + chunk.SpawnContent(a.ChunkSize) } local := a.toChunkLocal(v) tileId := local.X + local.Y*a.ChunkSize - if tileId >= len(a.Chunks[chunk].Tiles) { - return 0, fmt.Errorf("location outside of allocated chunk") + + // Sanity check + if tileId >= len(chunk.Tiles) || tileId < 0 { + log.Fatalf("Local tileID is not in valid chunk, somehow, this means something is very wrong") } - return a.Chunks[chunk].Tiles[tileId], nil + return chunk.Tiles[tileId] +} + +// toChunkWithGrow will expand the atlas for a given tile, returns the new chunk +func (a *Atlas) toChunkWithGrow(v vector.Vector) int { + for { + // Get the chunk, and grow looping until we have a valid chunk + chunk := a.toChunk(v) + if chunk >= len(a.Chunks) || chunk < 0 { + a.grow() + } else { + return chunk + } + } } // toChunkLocal gets a chunk local coordinate for a tile @@ -131,7 +114,7 @@ func (a *Atlas) toChunkLocal(v vector.Vector) vector.Vector { return vector.Vector{X: maths.Pmod(v.X, a.ChunkSize), Y: maths.Pmod(v.Y, a.ChunkSize)} } -// GetChunkID gets the chunk ID for a position in the world +// GetChunkID gets the current chunk ID for a position in the world func (a *Atlas) toChunk(v vector.Vector) int { local := a.toChunkLocal(v) // Get the chunk origin itself @@ -139,50 +122,34 @@ func (a *Atlas) toChunk(v vector.Vector) int { // Divided it by the number of chunks origin = origin.Divided(a.ChunkSize) // Shift it by our size (our origin is in the middle) - origin = origin.Added(vector.Vector{X: a.Size / 2, Y: a.Size / 2}) + origin = origin.Added(vector.Vector{X: a.CurrentSize / 2, Y: a.CurrentSize / 2}) // Get the ID based on the final values - return (a.Size * origin.Y) + origin.X + return (a.CurrentSize * origin.Y) + origin.X } // chunkOrigin gets the chunk origin for a given chunk index func (a *Atlas) chunkOrigin(chunk int) vector.Vector { v := vector.Vector{ - X: maths.Pmod(chunk, a.Size) - (a.Size / 2), - Y: (chunk / a.Size) - (a.Size / 2), + X: maths.Pmod(chunk, a.CurrentSize) - (a.CurrentSize / 2), + Y: (chunk / a.CurrentSize) - (a.CurrentSize / 2), } return v.Multiplied(a.ChunkSize) } -// GetWorldExtent gets the min and max valid coordinates of world -func (a *Atlas) GetWorldExtents() (min, max vector.Vector) { - min = vector.Vector{ - X: -(a.Size / 2) * a.ChunkSize, - Y: -(a.Size / 2) * a.ChunkSize, - } - max = vector.Vector{ - X: -min.X - 1, - Y: -min.Y - 1, - } - return -} - -// Grow will return a grown copy of the current atlas -func (a *Atlas) Grow(size int) error { - if size%2 != 0 { - return fmt.Errorf("atlas size must always be even") - } - delta := size - a.Size - if delta < 0 { - return fmt.Errorf("cannot shrink an atlas") - } else if delta == 0 { - return nil - } - +// grow will expand the current atlas in all directions by one chunk +func (a *Atlas) grow() error { // Create a new atlas - newAtlas := NewAtlas(size, a.ChunkSize) + newAtlas := NewAtlas(a.ChunkSize) - // Copy old chunks into new chunks + // Expand by one on each axis + newAtlas.CurrentSize = a.CurrentSize + 2 + + // Allocate the new atlas chunks + // These chunks will have nil tile slices + newAtlas.Chunks = make([]Chunk, newAtlas.CurrentSize*newAtlas.CurrentSize) + + // Copy all old chunks into the new atlas for index, chunk := range a.Chunks { // Calculate the new chunk location and copy over the data newAtlas.Chunks[newAtlas.toChunk(a.chunkOrigin(index))] = chunk diff --git a/pkg/atlas/atlas_test.go b/pkg/atlas/atlas_test.go index f8cc63a..3aeb328 100644 --- a/pkg/atlas/atlas_test.go +++ b/pkg/atlas/atlas_test.go @@ -3,60 +3,63 @@ package atlas import ( "testing" - "github.com/mdiluz/rove/pkg/objects" "github.com/mdiluz/rove/pkg/vector" "github.com/stretchr/testify/assert" ) func TestAtlas_NewAtlas(t *testing.T) { - a := NewAtlas(2, 1) + a := NewAtlas(1) assert.NotNil(t, a) - // Tiles should look like: 2 | 3 - // ----- - // 0 | 1 - assert.Equal(t, 4, len(a.Chunks)) - - a = NewAtlas(4, 1) - assert.NotNil(t, a) - // Tiles should look like: 2 | 3 - // ----- - // 0 | 1 - assert.Equal(t, 16, len(a.Chunks)) + assert.Equal(t, 1, a.ChunkSize) + assert.Equal(t, 0, len(a.Chunks)) // Should start empty } func TestAtlas_toChunk(t *testing.T) { - a := NewAtlas(2, 1) + a := NewAtlas(1) assert.NotNil(t, a) - // Tiles should look like: 2 | 3 + + // We start empty so we'll look like this + chunkID := a.toChunk(vector.Vector{X: 0, Y: 0}) + assert.Equal(t, 0, chunkID) + + // Get a tile to spawn the chunks + a.GetTile(vector.Vector{}) + + // Chunks should look like: + // 2 | 3 // ----- // 0 | 1 - tile := a.toChunk(vector.Vector{X: 0, Y: 0}) - assert.Equal(t, 3, tile) - tile = a.toChunk(vector.Vector{X: 0, Y: -1}) - assert.Equal(t, 1, tile) - tile = a.toChunk(vector.Vector{X: -1, Y: -1}) - assert.Equal(t, 0, tile) - tile = a.toChunk(vector.Vector{X: -1, Y: 0}) - assert.Equal(t, 2, tile) + chunkID = a.toChunk(vector.Vector{X: 0, Y: 0}) + assert.Equal(t, 3, chunkID) + chunkID = a.toChunk(vector.Vector{X: 0, Y: -1}) + assert.Equal(t, 1, chunkID) + chunkID = a.toChunk(vector.Vector{X: -1, Y: -1}) + assert.Equal(t, 0, chunkID) + chunkID = a.toChunk(vector.Vector{X: -1, Y: 0}) + assert.Equal(t, 2, chunkID) - a = NewAtlas(2, 2) + a = NewAtlas(2) assert.NotNil(t, a) - // Tiles should look like: + // Get a tile to spawn the chunks + a.GetTile(vector.Vector{}) + // Chunks should look like: // 2 | 3 // ----- // 0 | 1 - tile = a.toChunk(vector.Vector{X: 1, Y: 1}) - assert.Equal(t, 3, tile) - tile = a.toChunk(vector.Vector{X: 1, Y: -2}) - assert.Equal(t, 1, tile) - tile = a.toChunk(vector.Vector{X: -2, Y: -2}) - assert.Equal(t, 0, tile) - tile = a.toChunk(vector.Vector{X: -2, Y: 1}) - assert.Equal(t, 2, tile) + chunkID = a.toChunk(vector.Vector{X: 1, Y: 1}) + assert.Equal(t, 3, chunkID) + chunkID = a.toChunk(vector.Vector{X: 1, Y: -2}) + assert.Equal(t, 1, chunkID) + chunkID = a.toChunk(vector.Vector{X: -2, Y: -2}) + assert.Equal(t, 0, chunkID) + chunkID = a.toChunk(vector.Vector{X: -2, Y: 1}) + assert.Equal(t, 2, chunkID) - a = NewAtlas(4, 2) + a = NewAtlas(2) assert.NotNil(t, a) - // Tiles should look like: + // Get a tile to spawn the chunks + a.GetTile(vector.Vector{X: 0, Y: 3}) + // Chunks should look like: // 12| 13|| 14| 15 // ---------------- // 8 | 9 || 10| 11 @@ -64,107 +67,58 @@ func TestAtlas_toChunk(t *testing.T) { // 4 | 5 || 6 | 7 // ---------------- // 0 | 1 || 2 | 3 - tile = a.toChunk(vector.Vector{X: 1, Y: 3}) - assert.Equal(t, 14, tile) - tile = a.toChunk(vector.Vector{X: 1, Y: -3}) - assert.Equal(t, 2, tile) - tile = a.toChunk(vector.Vector{X: -1, Y: -1}) - assert.Equal(t, 5, tile) - tile = a.toChunk(vector.Vector{X: -2, Y: 2}) - assert.Equal(t, 13, tile) + chunkID = a.toChunk(vector.Vector{X: 1, Y: 3}) + assert.Equal(t, 14, chunkID) + chunkID = a.toChunk(vector.Vector{X: 1, Y: -3}) + assert.Equal(t, 2, chunkID) + chunkID = a.toChunk(vector.Vector{X: -1, Y: -1}) + assert.Equal(t, 5, chunkID) + chunkID = a.toChunk(vector.Vector{X: -2, Y: 2}) + assert.Equal(t, 13, chunkID) } func TestAtlas_GetSetTile(t *testing.T) { - a := NewAtlas(4, 10) + a := NewAtlas(10) assert.NotNil(t, a) // Set the origin tile to 1 and test it - assert.NoError(t, a.SetTile(vector.Vector{X: 0, Y: 0}, 1)) - tile, err := a.GetTile(vector.Vector{X: 0, Y: 0}) - assert.NoError(t, err) + a.SetTile(vector.Vector{X: 0, Y: 0}, 1) + tile := a.GetTile(vector.Vector{X: 0, Y: 0}) assert.Equal(t, byte(1), tile) // Set another tile to 1 and test it - assert.NoError(t, a.SetTile(vector.Vector{X: 5, Y: -2}, 2)) - tile, err = a.GetTile(vector.Vector{X: 5, Y: -2}) - assert.NoError(t, err) + a.SetTile(vector.Vector{X: 5, Y: -2}, 2) + tile = a.GetTile(vector.Vector{X: 5, Y: -2}) assert.Equal(t, byte(2), tile) } func TestAtlas_Grown(t *testing.T) { // Start with a small example - a := NewAtlas(2, 2) + a := NewAtlas(2) assert.NotNil(t, a) - assert.Equal(t, 4, len(a.Chunks)) + assert.Equal(t, 0, len(a.Chunks)) // Set a few tiles to values - assert.NoError(t, a.SetTile(vector.Vector{X: 0, Y: 0}, 1)) - assert.NoError(t, a.SetTile(vector.Vector{X: -1, Y: -1}, 2)) - assert.NoError(t, a.SetTile(vector.Vector{X: 1, Y: -2}, 3)) + a.SetTile(vector.Vector{X: 0, Y: 0}, 1) + a.SetTile(vector.Vector{X: -1, Y: -1}, 2) + a.SetTile(vector.Vector{X: 1, Y: -2}, 3) - // Grow once to just double it - err := a.Grow(4) - assert.NoError(t, err) - assert.Equal(t, 16, len(a.Chunks)) - - tile, err := a.GetTile(vector.Vector{X: 0, Y: 0}) - assert.NoError(t, err) + // Check tile values + tile := a.GetTile(vector.Vector{X: 0, Y: 0}) assert.Equal(t, byte(1), tile) - tile, err = a.GetTile(vector.Vector{X: -1, Y: -1}) - assert.NoError(t, err) + tile = a.GetTile(vector.Vector{X: -1, Y: -1}) assert.Equal(t, byte(2), tile) - tile, err = a.GetTile(vector.Vector{X: 1, Y: -2}) - assert.NoError(t, err) + tile = a.GetTile(vector.Vector{X: 1, Y: -2}) assert.Equal(t, byte(3), tile) - // Grow it again even bigger - err = a.Grow(10) - assert.NoError(t, err) - assert.Equal(t, 100, len(a.Chunks)) - - tile, err = a.GetTile(vector.Vector{X: 0, Y: 0}) - assert.NoError(t, err) + tile = a.GetTile(vector.Vector{X: 0, Y: 0}) assert.Equal(t, byte(1), tile) - tile, err = a.GetTile(vector.Vector{X: -1, Y: -1}) - assert.NoError(t, err) + tile = a.GetTile(vector.Vector{X: -1, Y: -1}) assert.Equal(t, byte(2), tile) - tile, err = a.GetTile(vector.Vector{X: 1, Y: -2}) - assert.NoError(t, err) + tile = a.GetTile(vector.Vector{X: 1, Y: -2}) assert.Equal(t, byte(3), tile) } - -func TestAtlas_SpawnWorld(t *testing.T) { - // Start with a small example - a := NewAtlas(2, 4) - assert.NotNil(t, a) - assert.Equal(t, 4, len(a.Chunks)) - assert.NoError(t, a.SpawnWalls()) - - for i := -4; i < 4; i++ { - tile, err := a.GetTile(vector.Vector{X: i, Y: -4}) - assert.NoError(t, err) - assert.Equal(t, objects.LargeRock, tile) - } - - for i := -4; i < 4; i++ { - tile, err := a.GetTile(vector.Vector{X: -4, Y: i}) - assert.NoError(t, err) - assert.Equal(t, objects.LargeRock, tile) - } - - for i := -4; i < 4; i++ { - tile, err := a.GetTile(vector.Vector{X: 3, Y: i}) - assert.NoError(t, err) - assert.Equal(t, objects.LargeRock, tile) - } - - for i := -4; i < 4; i++ { - tile, err := a.GetTile(vector.Vector{X: i, Y: 3}) - assert.NoError(t, err) - assert.Equal(t, objects.LargeRock, tile) - } -} diff --git a/pkg/game/command_test.go b/pkg/game/command_test.go index 11da854..3356f2a 100644 --- a/pkg/game/command_test.go +++ b/pkg/game/command_test.go @@ -8,7 +8,7 @@ import ( ) func TestCommand_Move(t *testing.T) { - world := NewWorld(2, 8) + world := NewWorld(8) a, err := world.SpawnRover() assert.NoError(t, err) pos := vector.Vector{ diff --git a/pkg/game/world.go b/pkg/game/world.go index 9048900..6b04594 100644 --- a/pkg/game/world.go +++ b/pkg/game/world.go @@ -12,7 +12,6 @@ import ( "github.com/google/uuid" "github.com/mdiluz/rove/pkg/atlas" "github.com/mdiluz/rove/pkg/bearing" - "github.com/mdiluz/rove/pkg/maths" "github.com/mdiluz/rove/pkg/objects" "github.com/mdiluz/rove/pkg/vector" ) @@ -44,7 +43,7 @@ type World struct { var wordsFile = os.Getenv("WORDS_FILE") // NewWorld creates a new world object -func NewWorld(size, chunkSize int) *World { +func NewWorld(chunkSize int) *World { // Try and load the words file var lines []string @@ -65,23 +64,11 @@ func NewWorld(size, chunkSize int) *World { Rovers: make(map[string]Rover), CommandQueue: make(map[string]CommandStream), Incoming: make(map[string]CommandStream), - Atlas: atlas.NewAtlas(size, chunkSize), + Atlas: atlas.NewAtlas(chunkSize), words: lines, } } -// SpawnWorld spawns a border at the edge of the world atlas -func (w *World) SpawnWorld(fillWorld bool) error { - w.worldMutex.Lock() - defer w.worldMutex.Unlock() - if fillWorld { - if err := w.Atlas.SpawnRocks(); err != nil { - return err - } - } - return w.Atlas.SpawnWalls() -} - // SpawnRover adds an rover to the game func (w *World) SpawnRover() (string, error) { w.worldMutex.Lock() @@ -114,16 +101,14 @@ func (w *World) SpawnRover() (string, error) { // Seach until we error (run out of world) for { - if tile, err := w.Atlas.GetTile(rover.Pos); err != nil { - return "", err + tile := w.Atlas.GetTile(rover.Pos) + if !objects.IsBlocking(tile) { + break } else { - if !objects.IsBlocking(tile) { - break - } else { - // Try and spawn to the east of the blockage - rover.Pos.Add(vector.Vector{X: 1, Y: 0}) - } + // Try and spawn to the east of the blockage + rover.Pos.Add(vector.Vector{X: 1, Y: 0}) } + } log.Printf("Spawned rover at %+v\n", rover.Pos) @@ -153,9 +138,7 @@ func (w *World) DestroyRover(rover string) error { if i, ok := w.Rovers[rover]; ok { // Clear the tile - if err := w.Atlas.SetTile(i.Pos, objects.Empty); err != nil { - return fmt.Errorf("coudln't clear old rover tile: %s", err) - } + w.Atlas.SetTile(i.Pos, objects.Empty) delete(w.Rovers, rover) } else { return fmt.Errorf("no rover matching id") @@ -213,9 +196,8 @@ func (w *World) WarpRover(rover string, pos vector.Vector) error { } // Check the tile is not blocked - if tile, err := w.Atlas.GetTile(pos); err != nil { - return fmt.Errorf("coudln't get state of destination rover tile: %s", err) - } else if objects.IsBlocking(tile) { + tile := w.Atlas.GetTile(pos) + if objects.IsBlocking(tile) { return fmt.Errorf("can't warp rover to occupied tile, check before warping") } @@ -237,9 +219,8 @@ func (w *World) MoveRover(rover string, b bearing.Bearing) (vector.Vector, error newPos := i.Pos.Added(b.Vector()) // Get the tile and verify it's empty - if tile, err := w.Atlas.GetTile(newPos); err != nil { - return vector.Vector{}, fmt.Errorf("couldn't get tile for new position: %s", err) - } else if !objects.IsBlocking(tile) { + tile := w.Atlas.GetTile(newPos) + if !objects.IsBlocking(tile) { // Perform the move i.Pos = newPos w.Rovers[rover] = i @@ -265,18 +246,12 @@ func (w *World) RoverStash(rover string) (byte, error) { defer w.worldMutex.Unlock() if r, ok := w.Rovers[rover]; ok { - if tile, err := w.Atlas.GetTile(r.Pos); err != nil { - return objects.Empty, err - } else { - if objects.IsStashable(tile) { - r.Inventory = append(r.Inventory, tile) - w.Rovers[rover] = r - if err := w.Atlas.SetTile(r.Pos, objects.Empty); err != nil { - return objects.Empty, err - } else { - return tile, nil - } - } + tile := w.Atlas.GetTile(r.Pos) + if objects.IsStashable(tile) { + r.Inventory = append(r.Inventory, tile) + w.Rovers[rover] = r + w.Atlas.SetTile(r.Pos, objects.Empty) + return tile, nil } } else { @@ -306,32 +281,19 @@ func (w *World) RadarFromRover(rover string) ([]byte, error) { Y: roverPos.Y + r.Range, } - // Make sure we only query within the actual world - worldMin, worldMax := w.Atlas.GetWorldExtents() - scanMin := vector.Vector{ - X: maths.Max(radarMin.X, worldMin.X), - Y: maths.Max(radarMin.Y, worldMin.Y), - } - scanMax := vector.Vector{ - X: maths.Min(radarMax.X, worldMax.X), - Y: maths.Min(radarMax.Y, worldMax.Y), - } - // Gather up all tiles within the range var radar = make([]byte, radarSpan*radarSpan) - for j := scanMin.Y; j <= scanMax.Y; j++ { - for i := scanMin.X; i <= scanMax.X; i++ { + for j := radarMin.Y; j <= radarMax.Y; j++ { + for i := radarMin.X; i <= radarMax.X; i++ { q := vector.Vector{X: i, Y: j} - if tile, err := w.Atlas.GetTile(q); err != nil { - return nil, fmt.Errorf("failed to query tile: %s", err) + tile := w.Atlas.GetTile(q) + + // Get the position relative to the bottom left of the radar + relative := q.Added(radarMin.Negated()) + index := relative.X + relative.Y*radarSpan + radar[index] = tile - } else { - // Get the position relative to the bottom left of the radar - relative := q.Added(radarMin.Negated()) - index := relative.X + relative.Y*radarSpan - radar[index] = tile - } } } diff --git a/pkg/game/world_test.go b/pkg/game/world_test.go index 5a48231..ceda417 100644 --- a/pkg/game/world_test.go +++ b/pkg/game/world_test.go @@ -11,14 +11,14 @@ import ( func TestNewWorld(t *testing.T) { // Very basic for now, nothing to verify - world := NewWorld(4, 4) + world := NewWorld(4) if world == nil { t.Error("Failed to create world") } } func TestWorld_CreateRover(t *testing.T) { - world := NewWorld(2, 8) + world := NewWorld(8) a, err := world.SpawnRover() assert.NoError(t, err) b, err := world.SpawnRover() @@ -33,7 +33,7 @@ func TestWorld_CreateRover(t *testing.T) { } func TestWorld_GetRover(t *testing.T) { - world := NewWorld(2, 4) + world := NewWorld(4) a, err := world.SpawnRover() assert.NoError(t, err) @@ -43,7 +43,7 @@ func TestWorld_GetRover(t *testing.T) { } func TestWorld_DestroyRover(t *testing.T) { - world := NewWorld(4, 1) + world := NewWorld(1) a, err := world.SpawnRover() assert.NoError(t, err) b, err := world.SpawnRover() @@ -61,7 +61,7 @@ func TestWorld_DestroyRover(t *testing.T) { } func TestWorld_GetSetMovePosition(t *testing.T) { - world := NewWorld(4, 4) + world := NewWorld(4) a, err := world.SpawnRover() assert.NoError(t, err) @@ -84,14 +84,14 @@ func TestWorld_GetSetMovePosition(t *testing.T) { assert.Equal(t, pos, newPos, "Failed to correctly move position for rover") // Place a tile in front of the rover - assert.NoError(t, world.Atlas.SetTile(vector.Vector{X: 0, Y: 2}, objects.LargeRock)) + world.Atlas.SetTile(vector.Vector{X: 0, Y: 2}, objects.LargeRock) newPos, err = world.MoveRover(a, b) assert.Equal(t, pos, newPos, "Failed to correctly not move position for rover into wall") } func TestWorld_RadarFromRover(t *testing.T) { // Create world that should have visible walls on the radar - world := NewWorld(4, 2) + world := NewWorld(2) a, err := world.SpawnRover() assert.NoError(t, err) b, err := world.SpawnRover() @@ -102,40 +102,18 @@ func TestWorld_RadarFromRover(t *testing.T) { assert.NoError(t, world.WarpRover(b, bpos), "Failed to warp rover") assert.NoError(t, world.WarpRover(a, vector.Vector{X: 0, Y: 0}), "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 := 4 + 4 + 1 assert.Equal(t, fullRange*fullRange, len(radar), "Radar returned wrong length") - // It should look like: - // --------- - // OOOOOOOO- - // O------O- - // O------O- - // O---R--O- - // O------O- - // O------O- - // OR-----O- - // OOOOOOOO- - PrintTiles(radar) - - // Test all expected values + // Test the expected values assert.Equal(t, objects.Rover, radar[1+fullRange]) assert.Equal(t, objects.Rover, radar[4+4*fullRange]) - for i := 0; i < 8; i++ { - assert.Equal(t, objects.LargeRock, radar[i]) - assert.Equal(t, objects.LargeRock, radar[i+(7*9)]) - assert.Equal(t, objects.LargeRock, radar[i*9]) - assert.Equal(t, objects.LargeRock, radar[(i*9)+7]) - } } func TestWorld_RoverStash(t *testing.T) { - world := NewWorld(2, 2) + world := NewWorld(2) a, err := world.SpawnRover() assert.NoError(t, err) @@ -147,15 +125,13 @@ func TestWorld_RoverStash(t *testing.T) { err = world.WarpRover(a, pos) assert.NoError(t, err, "Failed to set position for rover") - err = world.Atlas.SetTile(pos, objects.SmallRock) - assert.NoError(t, err, "Failed to set tile to rock") + world.Atlas.SetTile(pos, objects.SmallRock) o, err := world.RoverStash(a) assert.NoError(t, err, "Failed to stash") assert.Equal(t, objects.SmallRock, o, "Failed to get correct object") - tile, err := world.Atlas.GetTile(pos) - assert.NoError(t, err, "Failed to get tile") + tile := world.Atlas.GetTile(pos) assert.Equal(t, objects.Empty, tile, "Stash failed to remove object from atlas") inv, err := world.RoverInventory(a) @@ -164,7 +140,7 @@ func TestWorld_RoverStash(t *testing.T) { } func TestWorld_RoverDamage(t *testing.T) { - world := NewWorld(2, 2) + world := NewWorld(2) a, err := world.SpawnRover() assert.NoError(t, err) @@ -179,8 +155,7 @@ func TestWorld_RoverDamage(t *testing.T) { info, err := world.GetRover(a) assert.NoError(t, err, "couldn't get rover info") - err = world.Atlas.SetTile(vector.Vector{X: 0.0, Y: 1.0}, objects.LargeRock) - assert.NoError(t, err, "Failed to set tile to rock") + world.Atlas.SetTile(vector.Vector{X: 0.0, Y: 1.0}, objects.LargeRock) vec, err := world.MoveRover(a, bearing.North) assert.NoError(t, err, "Failed to move rover") @@ -192,7 +167,7 @@ func TestWorld_RoverDamage(t *testing.T) { } func TestWorld_RoverRepair(t *testing.T) { - world := NewWorld(2, 2) + world := NewWorld(2) a, err := world.SpawnRover() assert.NoError(t, err) @@ -207,15 +182,13 @@ func TestWorld_RoverRepair(t *testing.T) { originalInfo, err := world.GetRover(a) assert.NoError(t, err, "couldn't get rover info") - err = world.Atlas.SetTile(pos, objects.SmallRock) - assert.NoError(t, err, "Failed to set tile to rock") + world.Atlas.SetTile(pos, objects.SmallRock) o, err := world.RoverStash(a) assert.NoError(t, err, "Failed to stash") assert.Equal(t, objects.SmallRock, o, "Failed to get correct object") - err = world.Atlas.SetTile(vector.Vector{X: 0.0, Y: 1.0}, objects.LargeRock) - assert.NoError(t, err, "Failed to set tile to rock") + world.Atlas.SetTile(vector.Vector{X: 0.0, Y: 1.0}, objects.LargeRock) vec, err := world.MoveRover(a, bearing.North) assert.NoError(t, err, "Failed to move rover")