Convert Atlas to infinite lazy growth
The atlas will now expand as needed for any query, but only initialise the chunk tile memory when requested While this may still be a pre-mature optimisation, it does simplify some code and ensures that our memory footprint stays small, for the most part
This commit is contained in:
		
							parent
							
								
									2556c0d049
								
							
						
					
					
						commit
						b116cdf291
					
				
					 6 changed files with 186 additions and 337 deletions
				
			
		| 
						 | 
					@ -83,6 +83,7 @@ func NewServer(opts ...ServerOption) *Server {
 | 
				
			||||||
		address:     "",
 | 
							address:     "",
 | 
				
			||||||
		persistence: EphemeralData,
 | 
							persistence: EphemeralData,
 | 
				
			||||||
		schedule:    cron.New(),
 | 
							schedule:    cron.New(),
 | 
				
			||||||
 | 
							world:       game.NewWorld(16),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Apply all options
 | 
						// Apply all options
 | 
				
			||||||
| 
						 | 
					@ -90,9 +91,6 @@ func NewServer(opts ...ServerOption) *Server {
 | 
				
			||||||
		o(s)
 | 
							o(s)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Start small, we can grow the world later
 | 
					 | 
				
			||||||
	s.world = game.NewWorld(4, 8)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return s
 | 
						return s
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -114,11 +112,6 @@ func (s *Server) Initialise(fillWorld bool) (err error) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	s.accountant = accounts.NewAccountantClient(s.clientConn)
 | 
						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
 | 
						// Load the world file
 | 
				
			||||||
	if err := s.LoadWorld(); err != nil {
 | 
						if err := s.LoadWorld(); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
package atlas
 | 
					package atlas
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"math/rand"
 | 
						"math/rand"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,114 +15,98 @@ type Chunk struct {
 | 
				
			||||||
	Tiles []byte `json:"tiles"`
 | 
						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
 | 
					// Atlas represents a grid of Chunks
 | 
				
			||||||
type Atlas struct {
 | 
					type Atlas struct {
 | 
				
			||||||
	// Chunks represents all chunks in the world
 | 
						// Chunks represents all chunks in the world
 | 
				
			||||||
	// This is intentionally not a 2D array so it can be expanded in all directions
 | 
						// This is intentionally not a 2D array so it can be expanded in all directions
 | 
				
			||||||
	Chunks []Chunk `json:"chunks"`
 | 
						Chunks []Chunk `json:"chunks"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// size is the current width/height of the given atlas
 | 
						// CurrentSize is the current width/height of the given atlas
 | 
				
			||||||
	Size int `json:"size"`
 | 
						CurrentSize int `json:"currentSize"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// ChunkSize is the dimensions of each chunk
 | 
						// ChunkSize is the dimensions of each chunk
 | 
				
			||||||
	ChunkSize int `json:"chunksize"`
 | 
						ChunkSize int `json:"chunksize"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewAtlas creates a new empty atlas
 | 
					// NewAtlas creates a new empty atlas
 | 
				
			||||||
func NewAtlas(size, chunkSize int) Atlas {
 | 
					func NewAtlas(chunkSize int) Atlas {
 | 
				
			||||||
	if size%2 != 0 {
 | 
						return Atlas{
 | 
				
			||||||
		log.Fatal("atlas size must always be even")
 | 
							CurrentSize: 0,
 | 
				
			||||||
	}
 | 
							Chunks:      nil,
 | 
				
			||||||
 | 
					 | 
				
			||||||
	a := Atlas{
 | 
					 | 
				
			||||||
		Size:      size,
 | 
					 | 
				
			||||||
		Chunks:    make([]Chunk, size*size),
 | 
					 | 
				
			||||||
		ChunkSize:   chunkSize,
 | 
							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
 | 
					// SetTile sets an individual tile's kind
 | 
				
			||||||
func (a *Atlas) SetTile(v vector.Vector, tile byte) error {
 | 
					func (a *Atlas) SetTile(v vector.Vector, tile byte) {
 | 
				
			||||||
	chunk := a.toChunk(v)
 | 
						// Get the chunk, expand, and spawn it if needed
 | 
				
			||||||
	if chunk >= len(a.Chunks) {
 | 
						c := a.toChunkWithGrow(v)
 | 
				
			||||||
		return fmt.Errorf("location outside of allocated atlas")
 | 
						chunk := a.Chunks[c]
 | 
				
			||||||
 | 
						if chunk.Tiles == nil {
 | 
				
			||||||
 | 
							chunk.SpawnContent(a.ChunkSize)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local := a.toChunkLocal(v)
 | 
						local := a.toChunkLocal(v)
 | 
				
			||||||
	tileId := local.X + local.Y*a.ChunkSize
 | 
						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
 | 
					// GetTile will return an individual tile
 | 
				
			||||||
func (a *Atlas) GetTile(v vector.Vector) (byte, error) {
 | 
					func (a *Atlas) GetTile(v vector.Vector) byte {
 | 
				
			||||||
	chunk := a.toChunk(v)
 | 
						// Get the chunk, expand, and spawn it if needed
 | 
				
			||||||
	if chunk >= len(a.Chunks) {
 | 
						c := a.toChunkWithGrow(v)
 | 
				
			||||||
		return 0, fmt.Errorf("location outside of allocated atlas")
 | 
						chunk := a.Chunks[c]
 | 
				
			||||||
 | 
						if chunk.Tiles == nil {
 | 
				
			||||||
 | 
							chunk.SpawnContent(a.ChunkSize)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local := a.toChunkLocal(v)
 | 
						local := a.toChunkLocal(v)
 | 
				
			||||||
	tileId := local.X + local.Y*a.ChunkSize
 | 
						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
 | 
					// 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)}
 | 
						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 {
 | 
					func (a *Atlas) toChunk(v vector.Vector) int {
 | 
				
			||||||
	local := a.toChunkLocal(v)
 | 
						local := a.toChunkLocal(v)
 | 
				
			||||||
	// Get the chunk origin itself
 | 
						// Get the chunk origin itself
 | 
				
			||||||
| 
						 | 
					@ -139,50 +122,34 @@ func (a *Atlas) toChunk(v vector.Vector) int {
 | 
				
			||||||
	// Divided it by the number of chunks
 | 
						// Divided it by the number of chunks
 | 
				
			||||||
	origin = origin.Divided(a.ChunkSize)
 | 
						origin = origin.Divided(a.ChunkSize)
 | 
				
			||||||
	// Shift it by our size (our origin is in the middle)
 | 
						// 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
 | 
						// 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
 | 
					// chunkOrigin gets the chunk origin for a given chunk index
 | 
				
			||||||
func (a *Atlas) chunkOrigin(chunk int) vector.Vector {
 | 
					func (a *Atlas) chunkOrigin(chunk int) vector.Vector {
 | 
				
			||||||
	v := vector.Vector{
 | 
						v := vector.Vector{
 | 
				
			||||||
		X: maths.Pmod(chunk, a.Size) - (a.Size / 2),
 | 
							X: maths.Pmod(chunk, a.CurrentSize) - (a.CurrentSize / 2),
 | 
				
			||||||
		Y: (chunk / a.Size) - (a.Size / 2),
 | 
							Y: (chunk / a.CurrentSize) - (a.CurrentSize / 2),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return v.Multiplied(a.ChunkSize)
 | 
						return v.Multiplied(a.ChunkSize)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetWorldExtent gets the min and max valid coordinates of world
 | 
					// grow will expand the current atlas in all directions by one chunk
 | 
				
			||||||
func (a *Atlas) GetWorldExtents() (min, max vector.Vector) {
 | 
					func (a *Atlas) grow() error {
 | 
				
			||||||
	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
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Create a new atlas
 | 
						// 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 {
 | 
						for index, chunk := range a.Chunks {
 | 
				
			||||||
		// Calculate the new chunk location and copy over the data
 | 
							// Calculate the new chunk location and copy over the data
 | 
				
			||||||
		newAtlas.Chunks[newAtlas.toChunk(a.chunkOrigin(index))] = chunk
 | 
							newAtlas.Chunks[newAtlas.toChunk(a.chunkOrigin(index))] = chunk
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,60 +3,63 @@ package atlas
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/mdiluz/rove/pkg/objects"
 | 
					 | 
				
			||||||
	"github.com/mdiluz/rove/pkg/vector"
 | 
						"github.com/mdiluz/rove/pkg/vector"
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestAtlas_NewAtlas(t *testing.T) {
 | 
					func TestAtlas_NewAtlas(t *testing.T) {
 | 
				
			||||||
	a := NewAtlas(2, 1)
 | 
						a := NewAtlas(1)
 | 
				
			||||||
	assert.NotNil(t, a)
 | 
						assert.NotNil(t, a)
 | 
				
			||||||
	// Tiles should look like: 2 | 3
 | 
						assert.Equal(t, 1, a.ChunkSize)
 | 
				
			||||||
	//  -----
 | 
						assert.Equal(t, 0, len(a.Chunks)) // Should start empty
 | 
				
			||||||
	//  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))
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestAtlas_toChunk(t *testing.T) {
 | 
					func TestAtlas_toChunk(t *testing.T) {
 | 
				
			||||||
	a := NewAtlas(2, 1)
 | 
						a := NewAtlas(1)
 | 
				
			||||||
	assert.NotNil(t, a)
 | 
						assert.NotNil(t, a)
 | 
				
			||||||
	// Tiles 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)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	a = NewAtlas(2, 2)
 | 
						// We start empty so we'll look like this
 | 
				
			||||||
	assert.NotNil(t, a)
 | 
						chunkID := a.toChunk(vector.Vector{X: 0, Y: 0})
 | 
				
			||||||
	// Tiles should look like:
 | 
						assert.Equal(t, 0, chunkID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get a tile to spawn the chunks
 | 
				
			||||||
 | 
						a.GetTile(vector.Vector{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Chunks should look like:
 | 
				
			||||||
	//  2 | 3
 | 
						//  2 | 3
 | 
				
			||||||
	//  -----
 | 
						//  -----
 | 
				
			||||||
	//  0 | 1
 | 
						//  0 | 1
 | 
				
			||||||
	tile = a.toChunk(vector.Vector{X: 1, Y: 1})
 | 
						chunkID = a.toChunk(vector.Vector{X: 0, Y: 0})
 | 
				
			||||||
	assert.Equal(t, 3, tile)
 | 
						assert.Equal(t, 3, chunkID)
 | 
				
			||||||
	tile = a.toChunk(vector.Vector{X: 1, Y: -2})
 | 
						chunkID = a.toChunk(vector.Vector{X: 0, Y: -1})
 | 
				
			||||||
	assert.Equal(t, 1, tile)
 | 
						assert.Equal(t, 1, chunkID)
 | 
				
			||||||
	tile = a.toChunk(vector.Vector{X: -2, Y: -2})
 | 
						chunkID = a.toChunk(vector.Vector{X: -1, Y: -1})
 | 
				
			||||||
	assert.Equal(t, 0, tile)
 | 
						assert.Equal(t, 0, chunkID)
 | 
				
			||||||
	tile = a.toChunk(vector.Vector{X: -2, Y: 1})
 | 
						chunkID = a.toChunk(vector.Vector{X: -1, Y: 0})
 | 
				
			||||||
	assert.Equal(t, 2, tile)
 | 
						assert.Equal(t, 2, chunkID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	a = NewAtlas(4, 2)
 | 
						a = NewAtlas(2)
 | 
				
			||||||
	assert.NotNil(t, a)
 | 
						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
 | 
				
			||||||
 | 
						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(2)
 | 
				
			||||||
 | 
						assert.NotNil(t, a)
 | 
				
			||||||
 | 
						// Get a tile to spawn the chunks
 | 
				
			||||||
 | 
						a.GetTile(vector.Vector{X: 0, Y: 3})
 | 
				
			||||||
 | 
						// Chunks should look like:
 | 
				
			||||||
	//  12| 13|| 14| 15
 | 
						//  12| 13|| 14| 15
 | 
				
			||||||
	// ----------------
 | 
						// ----------------
 | 
				
			||||||
	//  8 | 9 || 10| 11
 | 
						//  8 | 9 || 10| 11
 | 
				
			||||||
| 
						 | 
					@ -64,107 +67,58 @@ func TestAtlas_toChunk(t *testing.T) {
 | 
				
			||||||
	//  4 | 5 || 6 | 7
 | 
						//  4 | 5 || 6 | 7
 | 
				
			||||||
	// ----------------
 | 
						// ----------------
 | 
				
			||||||
	//  0 | 1 || 2 | 3
 | 
						//  0 | 1 || 2 | 3
 | 
				
			||||||
	tile = a.toChunk(vector.Vector{X: 1, Y: 3})
 | 
						chunkID = a.toChunk(vector.Vector{X: 1, Y: 3})
 | 
				
			||||||
	assert.Equal(t, 14, tile)
 | 
						assert.Equal(t, 14, chunkID)
 | 
				
			||||||
	tile = a.toChunk(vector.Vector{X: 1, Y: -3})
 | 
						chunkID = a.toChunk(vector.Vector{X: 1, Y: -3})
 | 
				
			||||||
	assert.Equal(t, 2, tile)
 | 
						assert.Equal(t, 2, chunkID)
 | 
				
			||||||
	tile = a.toChunk(vector.Vector{X: -1, Y: -1})
 | 
						chunkID = a.toChunk(vector.Vector{X: -1, Y: -1})
 | 
				
			||||||
	assert.Equal(t, 5, tile)
 | 
						assert.Equal(t, 5, chunkID)
 | 
				
			||||||
	tile = a.toChunk(vector.Vector{X: -2, Y: 2})
 | 
						chunkID = a.toChunk(vector.Vector{X: -2, Y: 2})
 | 
				
			||||||
	assert.Equal(t, 13, tile)
 | 
						assert.Equal(t, 13, chunkID)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestAtlas_GetSetTile(t *testing.T) {
 | 
					func TestAtlas_GetSetTile(t *testing.T) {
 | 
				
			||||||
	a := NewAtlas(4, 10)
 | 
						a := NewAtlas(10)
 | 
				
			||||||
	assert.NotNil(t, a)
 | 
						assert.NotNil(t, a)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Set the origin tile to 1 and test it
 | 
						// Set the origin tile to 1 and test it
 | 
				
			||||||
	assert.NoError(t, a.SetTile(vector.Vector{X: 0, Y: 0}, 1))
 | 
						a.SetTile(vector.Vector{X: 0, Y: 0}, 1)
 | 
				
			||||||
	tile, err := a.GetTile(vector.Vector{X: 0, Y: 0})
 | 
						tile := a.GetTile(vector.Vector{X: 0, Y: 0})
 | 
				
			||||||
	assert.NoError(t, err)
 | 
					 | 
				
			||||||
	assert.Equal(t, byte(1), tile)
 | 
						assert.Equal(t, byte(1), tile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Set another tile to 1 and test it
 | 
						// Set another tile to 1 and test it
 | 
				
			||||||
	assert.NoError(t, a.SetTile(vector.Vector{X: 5, Y: -2}, 2))
 | 
						a.SetTile(vector.Vector{X: 5, Y: -2}, 2)
 | 
				
			||||||
	tile, err = a.GetTile(vector.Vector{X: 5, Y: -2})
 | 
						tile = a.GetTile(vector.Vector{X: 5, Y: -2})
 | 
				
			||||||
	assert.NoError(t, err)
 | 
					 | 
				
			||||||
	assert.Equal(t, byte(2), tile)
 | 
						assert.Equal(t, byte(2), tile)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestAtlas_Grown(t *testing.T) {
 | 
					func TestAtlas_Grown(t *testing.T) {
 | 
				
			||||||
	// Start with a small example
 | 
						// Start with a small example
 | 
				
			||||||
	a := NewAtlas(2, 2)
 | 
						a := NewAtlas(2)
 | 
				
			||||||
	assert.NotNil(t, a)
 | 
						assert.NotNil(t, a)
 | 
				
			||||||
	assert.Equal(t, 4, len(a.Chunks))
 | 
						assert.Equal(t, 0, len(a.Chunks))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Set a few tiles to values
 | 
						// Set a few tiles to values
 | 
				
			||||||
	assert.NoError(t, a.SetTile(vector.Vector{X: 0, Y: 0}, 1))
 | 
						a.SetTile(vector.Vector{X: 0, Y: 0}, 1)
 | 
				
			||||||
	assert.NoError(t, a.SetTile(vector.Vector{X: -1, Y: -1}, 2))
 | 
						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: 1, Y: -2}, 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Grow once to just double it
 | 
						// Check tile values
 | 
				
			||||||
	err := a.Grow(4)
 | 
						tile := a.GetTile(vector.Vector{X: 0, Y: 0})
 | 
				
			||||||
	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)
 | 
					 | 
				
			||||||
	assert.Equal(t, byte(1), tile)
 | 
						assert.Equal(t, byte(1), tile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tile, err = a.GetTile(vector.Vector{X: -1, Y: -1})
 | 
						tile = a.GetTile(vector.Vector{X: -1, Y: -1})
 | 
				
			||||||
	assert.NoError(t, err)
 | 
					 | 
				
			||||||
	assert.Equal(t, byte(2), tile)
 | 
						assert.Equal(t, byte(2), tile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tile, err = a.GetTile(vector.Vector{X: 1, Y: -2})
 | 
						tile = a.GetTile(vector.Vector{X: 1, Y: -2})
 | 
				
			||||||
	assert.NoError(t, err)
 | 
					 | 
				
			||||||
	assert.Equal(t, byte(3), tile)
 | 
						assert.Equal(t, byte(3), tile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Grow it again even bigger
 | 
						tile = a.GetTile(vector.Vector{X: 0, Y: 0})
 | 
				
			||||||
	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)
 | 
					 | 
				
			||||||
	assert.Equal(t, byte(1), tile)
 | 
						assert.Equal(t, byte(1), tile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tile, err = a.GetTile(vector.Vector{X: -1, Y: -1})
 | 
						tile = a.GetTile(vector.Vector{X: -1, Y: -1})
 | 
				
			||||||
	assert.NoError(t, err)
 | 
					 | 
				
			||||||
	assert.Equal(t, byte(2), tile)
 | 
						assert.Equal(t, byte(2), tile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tile, err = a.GetTile(vector.Vector{X: 1, Y: -2})
 | 
						tile = a.GetTile(vector.Vector{X: 1, Y: -2})
 | 
				
			||||||
	assert.NoError(t, err)
 | 
					 | 
				
			||||||
	assert.Equal(t, byte(3), tile)
 | 
						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)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ import (
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCommand_Move(t *testing.T) {
 | 
					func TestCommand_Move(t *testing.T) {
 | 
				
			||||||
	world := NewWorld(2, 8)
 | 
						world := NewWorld(8)
 | 
				
			||||||
	a, err := world.SpawnRover()
 | 
						a, err := world.SpawnRover()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	pos := vector.Vector{
 | 
						pos := vector.Vector{
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,6 @@ import (
 | 
				
			||||||
	"github.com/google/uuid"
 | 
						"github.com/google/uuid"
 | 
				
			||||||
	"github.com/mdiluz/rove/pkg/atlas"
 | 
						"github.com/mdiluz/rove/pkg/atlas"
 | 
				
			||||||
	"github.com/mdiluz/rove/pkg/bearing"
 | 
						"github.com/mdiluz/rove/pkg/bearing"
 | 
				
			||||||
	"github.com/mdiluz/rove/pkg/maths"
 | 
					 | 
				
			||||||
	"github.com/mdiluz/rove/pkg/objects"
 | 
						"github.com/mdiluz/rove/pkg/objects"
 | 
				
			||||||
	"github.com/mdiluz/rove/pkg/vector"
 | 
						"github.com/mdiluz/rove/pkg/vector"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -44,7 +43,7 @@ type World struct {
 | 
				
			||||||
var wordsFile = os.Getenv("WORDS_FILE")
 | 
					var wordsFile = os.Getenv("WORDS_FILE")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewWorld creates a new world object
 | 
					// NewWorld creates a new world object
 | 
				
			||||||
func NewWorld(size, chunkSize int) *World {
 | 
					func NewWorld(chunkSize int) *World {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Try and load the words file
 | 
						// Try and load the words file
 | 
				
			||||||
	var lines []string
 | 
						var lines []string
 | 
				
			||||||
| 
						 | 
					@ -65,23 +64,11 @@ func NewWorld(size, chunkSize int) *World {
 | 
				
			||||||
		Rovers:       make(map[string]Rover),
 | 
							Rovers:       make(map[string]Rover),
 | 
				
			||||||
		CommandQueue: make(map[string]CommandStream),
 | 
							CommandQueue: make(map[string]CommandStream),
 | 
				
			||||||
		Incoming:     make(map[string]CommandStream),
 | 
							Incoming:     make(map[string]CommandStream),
 | 
				
			||||||
		Atlas:        atlas.NewAtlas(size, chunkSize),
 | 
							Atlas:        atlas.NewAtlas(chunkSize),
 | 
				
			||||||
		words:        lines,
 | 
							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
 | 
					// SpawnRover adds an rover to the game
 | 
				
			||||||
func (w *World) SpawnRover() (string, error) {
 | 
					func (w *World) SpawnRover() (string, error) {
 | 
				
			||||||
	w.worldMutex.Lock()
 | 
						w.worldMutex.Lock()
 | 
				
			||||||
| 
						 | 
					@ -114,16 +101,14 @@ func (w *World) SpawnRover() (string, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Seach until we error (run out of world)
 | 
						// Seach until we error (run out of world)
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		if tile, err := w.Atlas.GetTile(rover.Pos); err != nil {
 | 
							tile := w.Atlas.GetTile(rover.Pos)
 | 
				
			||||||
			return "", err
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
		if !objects.IsBlocking(tile) {
 | 
							if !objects.IsBlocking(tile) {
 | 
				
			||||||
			break
 | 
								break
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			// Try and spawn to the east of the blockage
 | 
								// Try and spawn to the east of the blockage
 | 
				
			||||||
			rover.Pos.Add(vector.Vector{X: 1, Y: 0})
 | 
								rover.Pos.Add(vector.Vector{X: 1, Y: 0})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		}
 | 
					
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Printf("Spawned rover at %+v\n", rover.Pos)
 | 
						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 {
 | 
						if i, ok := w.Rovers[rover]; ok {
 | 
				
			||||||
		// Clear the tile
 | 
							// Clear the tile
 | 
				
			||||||
		if err := w.Atlas.SetTile(i.Pos, objects.Empty); err != nil {
 | 
							w.Atlas.SetTile(i.Pos, objects.Empty)
 | 
				
			||||||
			return fmt.Errorf("coudln't clear old rover tile: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		delete(w.Rovers, rover)
 | 
							delete(w.Rovers, rover)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		return fmt.Errorf("no rover matching id")
 | 
							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
 | 
							// Check the tile is not blocked
 | 
				
			||||||
		if tile, err := w.Atlas.GetTile(pos); err != nil {
 | 
							tile := w.Atlas.GetTile(pos)
 | 
				
			||||||
			return fmt.Errorf("coudln't get state of destination rover tile: %s", err)
 | 
							if objects.IsBlocking(tile) {
 | 
				
			||||||
		} else if objects.IsBlocking(tile) {
 | 
					 | 
				
			||||||
			return fmt.Errorf("can't warp rover to occupied tile, check before warping")
 | 
								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())
 | 
							newPos := i.Pos.Added(b.Vector())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Get the tile and verify it's empty
 | 
							// Get the tile and verify it's empty
 | 
				
			||||||
		if tile, err := w.Atlas.GetTile(newPos); err != nil {
 | 
							tile := w.Atlas.GetTile(newPos)
 | 
				
			||||||
			return vector.Vector{}, fmt.Errorf("couldn't get tile for new position: %s", err)
 | 
							if !objects.IsBlocking(tile) {
 | 
				
			||||||
		} else if !objects.IsBlocking(tile) {
 | 
					 | 
				
			||||||
			// Perform the move
 | 
								// Perform the move
 | 
				
			||||||
			i.Pos = newPos
 | 
								i.Pos = newPos
 | 
				
			||||||
			w.Rovers[rover] = i
 | 
								w.Rovers[rover] = i
 | 
				
			||||||
| 
						 | 
					@ -265,19 +246,13 @@ func (w *World) RoverStash(rover string) (byte, error) {
 | 
				
			||||||
	defer w.worldMutex.Unlock()
 | 
						defer w.worldMutex.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if r, ok := w.Rovers[rover]; ok {
 | 
						if r, ok := w.Rovers[rover]; ok {
 | 
				
			||||||
		if tile, err := w.Atlas.GetTile(r.Pos); err != nil {
 | 
							tile := w.Atlas.GetTile(r.Pos)
 | 
				
			||||||
			return objects.Empty, err
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
		if objects.IsStashable(tile) {
 | 
							if objects.IsStashable(tile) {
 | 
				
			||||||
			r.Inventory = append(r.Inventory, tile)
 | 
								r.Inventory = append(r.Inventory, tile)
 | 
				
			||||||
			w.Rovers[rover] = r
 | 
								w.Rovers[rover] = r
 | 
				
			||||||
				if err := w.Atlas.SetTile(r.Pos, objects.Empty); err != nil {
 | 
								w.Atlas.SetTile(r.Pos, objects.Empty)
 | 
				
			||||||
					return objects.Empty, err
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
			return tile, nil
 | 
								return tile, nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		return objects.Empty, fmt.Errorf("no rover matching id")
 | 
							return objects.Empty, fmt.Errorf("no rover matching id")
 | 
				
			||||||
| 
						 | 
					@ -306,32 +281,19 @@ func (w *World) RadarFromRover(rover string) ([]byte, error) {
 | 
				
			||||||
			Y: roverPos.Y + r.Range,
 | 
								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
 | 
							// Gather up all tiles within the range
 | 
				
			||||||
		var radar = make([]byte, radarSpan*radarSpan)
 | 
							var radar = make([]byte, radarSpan*radarSpan)
 | 
				
			||||||
		for j := scanMin.Y; j <= scanMax.Y; j++ {
 | 
							for j := radarMin.Y; j <= radarMax.Y; j++ {
 | 
				
			||||||
			for i := scanMin.X; i <= scanMax.X; i++ {
 | 
								for i := radarMin.X; i <= radarMax.X; i++ {
 | 
				
			||||||
				q := vector.Vector{X: i, Y: j}
 | 
									q := vector.Vector{X: i, Y: j}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if tile, err := w.Atlas.GetTile(q); err != nil {
 | 
									tile := w.Atlas.GetTile(q)
 | 
				
			||||||
					return nil, fmt.Errorf("failed to query tile: %s", err)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
				// Get the position relative to the bottom left of the radar
 | 
									// Get the position relative to the bottom left of the radar
 | 
				
			||||||
				relative := q.Added(radarMin.Negated())
 | 
									relative := q.Added(radarMin.Negated())
 | 
				
			||||||
				index := relative.X + relative.Y*radarSpan
 | 
									index := relative.X + relative.Y*radarSpan
 | 
				
			||||||
				radar[index] = tile
 | 
									radar[index] = tile
 | 
				
			||||||
				}
 | 
					
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,14 +11,14 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestNewWorld(t *testing.T) {
 | 
					func TestNewWorld(t *testing.T) {
 | 
				
			||||||
	// Very basic for now, nothing to verify
 | 
						// Very basic for now, nothing to verify
 | 
				
			||||||
	world := NewWorld(4, 4)
 | 
						world := NewWorld(4)
 | 
				
			||||||
	if world == nil {
 | 
						if world == nil {
 | 
				
			||||||
		t.Error("Failed to create world")
 | 
							t.Error("Failed to create world")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestWorld_CreateRover(t *testing.T) {
 | 
					func TestWorld_CreateRover(t *testing.T) {
 | 
				
			||||||
	world := NewWorld(2, 8)
 | 
						world := NewWorld(8)
 | 
				
			||||||
	a, err := world.SpawnRover()
 | 
						a, err := world.SpawnRover()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	b, err := world.SpawnRover()
 | 
						b, err := world.SpawnRover()
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,7 @@ func TestWorld_CreateRover(t *testing.T) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestWorld_GetRover(t *testing.T) {
 | 
					func TestWorld_GetRover(t *testing.T) {
 | 
				
			||||||
	world := NewWorld(2, 4)
 | 
						world := NewWorld(4)
 | 
				
			||||||
	a, err := world.SpawnRover()
 | 
						a, err := world.SpawnRover()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ func TestWorld_GetRover(t *testing.T) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestWorld_DestroyRover(t *testing.T) {
 | 
					func TestWorld_DestroyRover(t *testing.T) {
 | 
				
			||||||
	world := NewWorld(4, 1)
 | 
						world := NewWorld(1)
 | 
				
			||||||
	a, err := world.SpawnRover()
 | 
						a, err := world.SpawnRover()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	b, err := world.SpawnRover()
 | 
						b, err := world.SpawnRover()
 | 
				
			||||||
| 
						 | 
					@ -61,7 +61,7 @@ func TestWorld_DestroyRover(t *testing.T) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestWorld_GetSetMovePosition(t *testing.T) {
 | 
					func TestWorld_GetSetMovePosition(t *testing.T) {
 | 
				
			||||||
	world := NewWorld(4, 4)
 | 
						world := NewWorld(4)
 | 
				
			||||||
	a, err := world.SpawnRover()
 | 
						a, err := world.SpawnRover()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						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")
 | 
						assert.Equal(t, pos, newPos, "Failed to correctly move position for rover")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Place a tile in front of the 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)
 | 
						newPos, err = world.MoveRover(a, b)
 | 
				
			||||||
	assert.Equal(t, pos, newPos, "Failed to correctly not move position for rover into wall")
 | 
						assert.Equal(t, pos, newPos, "Failed to correctly not move position for rover into wall")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestWorld_RadarFromRover(t *testing.T) {
 | 
					func TestWorld_RadarFromRover(t *testing.T) {
 | 
				
			||||||
	// Create world that should have visible walls on the radar
 | 
						// Create world that should have visible walls on the radar
 | 
				
			||||||
	world := NewWorld(4, 2)
 | 
						world := NewWorld(2)
 | 
				
			||||||
	a, err := world.SpawnRover()
 | 
						a, err := world.SpawnRover()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	b, err := world.SpawnRover()
 | 
						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(b, bpos), "Failed to warp rover")
 | 
				
			||||||
	assert.NoError(t, world.WarpRover(a, vector.Vector{X: 0, Y: 0}), "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)
 | 
						radar, err := world.RadarFromRover(a)
 | 
				
			||||||
	assert.NoError(t, err, "Failed to get radar from rover")
 | 
						assert.NoError(t, err, "Failed to get radar from rover")
 | 
				
			||||||
	fullRange := 4 + 4 + 1
 | 
						fullRange := 4 + 4 + 1
 | 
				
			||||||
	assert.Equal(t, fullRange*fullRange, len(radar), "Radar returned wrong length")
 | 
						assert.Equal(t, fullRange*fullRange, len(radar), "Radar returned wrong length")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// It should look like:
 | 
						// Test the expected values
 | 
				
			||||||
	// ---------
 | 
					 | 
				
			||||||
	// OOOOOOOO-
 | 
					 | 
				
			||||||
	// O------O-
 | 
					 | 
				
			||||||
	// O------O-
 | 
					 | 
				
			||||||
	// O---R--O-
 | 
					 | 
				
			||||||
	// O------O-
 | 
					 | 
				
			||||||
	// O------O-
 | 
					 | 
				
			||||||
	// OR-----O-
 | 
					 | 
				
			||||||
	// OOOOOOOO-
 | 
					 | 
				
			||||||
	PrintTiles(radar)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Test all expected values
 | 
					 | 
				
			||||||
	assert.Equal(t, objects.Rover, radar[1+fullRange])
 | 
						assert.Equal(t, objects.Rover, radar[1+fullRange])
 | 
				
			||||||
	assert.Equal(t, objects.Rover, radar[4+4*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) {
 | 
					func TestWorld_RoverStash(t *testing.T) {
 | 
				
			||||||
	world := NewWorld(2, 2)
 | 
						world := NewWorld(2)
 | 
				
			||||||
	a, err := world.SpawnRover()
 | 
						a, err := world.SpawnRover()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -147,15 +125,13 @@ func TestWorld_RoverStash(t *testing.T) {
 | 
				
			||||||
	err = world.WarpRover(a, pos)
 | 
						err = world.WarpRover(a, pos)
 | 
				
			||||||
	assert.NoError(t, err, "Failed to set position for rover")
 | 
						assert.NoError(t, err, "Failed to set position for rover")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = world.Atlas.SetTile(pos, objects.SmallRock)
 | 
						world.Atlas.SetTile(pos, objects.SmallRock)
 | 
				
			||||||
	assert.NoError(t, err, "Failed to set tile to rock")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	o, err := world.RoverStash(a)
 | 
						o, err := world.RoverStash(a)
 | 
				
			||||||
	assert.NoError(t, err, "Failed to stash")
 | 
						assert.NoError(t, err, "Failed to stash")
 | 
				
			||||||
	assert.Equal(t, objects.SmallRock, o, "Failed to get correct object")
 | 
						assert.Equal(t, objects.SmallRock, o, "Failed to get correct object")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tile, err := world.Atlas.GetTile(pos)
 | 
						tile := world.Atlas.GetTile(pos)
 | 
				
			||||||
	assert.NoError(t, err, "Failed to get tile")
 | 
					 | 
				
			||||||
	assert.Equal(t, objects.Empty, tile, "Stash failed to remove object from atlas")
 | 
						assert.Equal(t, objects.Empty, tile, "Stash failed to remove object from atlas")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	inv, err := world.RoverInventory(a)
 | 
						inv, err := world.RoverInventory(a)
 | 
				
			||||||
| 
						 | 
					@ -164,7 +140,7 @@ func TestWorld_RoverStash(t *testing.T) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestWorld_RoverDamage(t *testing.T) {
 | 
					func TestWorld_RoverDamage(t *testing.T) {
 | 
				
			||||||
	world := NewWorld(2, 2)
 | 
						world := NewWorld(2)
 | 
				
			||||||
	a, err := world.SpawnRover()
 | 
						a, err := world.SpawnRover()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -179,8 +155,7 @@ func TestWorld_RoverDamage(t *testing.T) {
 | 
				
			||||||
	info, err := world.GetRover(a)
 | 
						info, err := world.GetRover(a)
 | 
				
			||||||
	assert.NoError(t, err, "couldn't get rover info")
 | 
						assert.NoError(t, err, "couldn't get rover info")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = world.Atlas.SetTile(vector.Vector{X: 0.0, Y: 1.0}, objects.LargeRock)
 | 
						world.Atlas.SetTile(vector.Vector{X: 0.0, Y: 1.0}, objects.LargeRock)
 | 
				
			||||||
	assert.NoError(t, err, "Failed to set tile to rock")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	vec, err := world.MoveRover(a, bearing.North)
 | 
						vec, err := world.MoveRover(a, bearing.North)
 | 
				
			||||||
	assert.NoError(t, err, "Failed to move rover")
 | 
						assert.NoError(t, err, "Failed to move rover")
 | 
				
			||||||
| 
						 | 
					@ -192,7 +167,7 @@ func TestWorld_RoverDamage(t *testing.T) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestWorld_RoverRepair(t *testing.T) {
 | 
					func TestWorld_RoverRepair(t *testing.T) {
 | 
				
			||||||
	world := NewWorld(2, 2)
 | 
						world := NewWorld(2)
 | 
				
			||||||
	a, err := world.SpawnRover()
 | 
						a, err := world.SpawnRover()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -207,15 +182,13 @@ func TestWorld_RoverRepair(t *testing.T) {
 | 
				
			||||||
	originalInfo, err := world.GetRover(a)
 | 
						originalInfo, err := world.GetRover(a)
 | 
				
			||||||
	assert.NoError(t, err, "couldn't get rover info")
 | 
						assert.NoError(t, err, "couldn't get rover info")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = world.Atlas.SetTile(pos, objects.SmallRock)
 | 
						world.Atlas.SetTile(pos, objects.SmallRock)
 | 
				
			||||||
	assert.NoError(t, err, "Failed to set tile to rock")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	o, err := world.RoverStash(a)
 | 
						o, err := world.RoverStash(a)
 | 
				
			||||||
	assert.NoError(t, err, "Failed to stash")
 | 
						assert.NoError(t, err, "Failed to stash")
 | 
				
			||||||
	assert.Equal(t, objects.SmallRock, o, "Failed to get correct object")
 | 
						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)
 | 
						world.Atlas.SetTile(vector.Vector{X: 0.0, Y: 1.0}, objects.LargeRock)
 | 
				
			||||||
	assert.NoError(t, err, "Failed to set tile to rock")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	vec, err := world.MoveRover(a, bearing.North)
 | 
						vec, err := world.MoveRover(a, bearing.North)
 | 
				
			||||||
	assert.NoError(t, err, "Failed to move rover")
 | 
						assert.NoError(t, err, "Failed to move rover")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue