Fix world spawning and radar

Also expand test coverage a little to ensure it's correct
This commit is contained in:
Marc Di Luzio 2020-06-08 23:02:09 +01:00
parent fba75960f8
commit 43588c0e4b
8 changed files with 130 additions and 45 deletions

View file

@ -174,10 +174,10 @@ func InnerMain(command string) error {
return fmt.Errorf("Server returned failure: %s", response.Error) return fmt.Errorf("Server returned failure: %s", response.Error)
} else { } else {
// Print the radar // Print out the radar
num := int(math.Sqrt(float64(len(response.Tiles)))) 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.Printf("%d", response.Tiles[i+num*j])
} }
fmt.Print("\n") fmt.Print("\n")

View file

@ -47,8 +47,8 @@ func NewAtlas(size int, chunkSize int) Atlas {
return a return a
} }
// SpawnWorld spawns the current world // SpawnRocks peppers the world with rocks
func (a *Atlas) SpawnWorld() error { func (a *Atlas) SpawnRocks() error {
extent := a.ChunkSize * (a.Size / 2) extent := a.ChunkSize * (a.Size / 2)
// Pepper the current world with rocks // 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 // Surround the atlas in walls
for i := -extent; i < extent; i++ { 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 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 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 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 return err
} }
} }

View file

@ -137,16 +137,32 @@ func TestAtlas_Grown(t *testing.T) {
func TestAtlas_SpawnWorld(t *testing.T) { func TestAtlas_SpawnWorld(t *testing.T) {
// Start with a small example // Start with a small example
a := NewAtlas(2, 2) a := NewAtlas(2, 4)
assert.NotNil(t, a) assert.NotNil(t, a)
assert.Equal(t, 4, len(a.Chunks)) assert.Equal(t, 4, len(a.Chunks))
assert.NoError(t, a.SpawnWalls())
assert.NoError(t, a.SpawnWorld()) for i := -4; i < 4; i++ {
tile, err := a.GetTile(Vector{1, 1}) tile, err := a.GetTile(Vector{i, -4})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, TileWall, tile) assert.Equal(t, TileWall, tile)
}
tile, err = a.GetTile(Vector{-2, -2}) for i := -4; i < 4; i++ {
tile, err := a.GetTile(Vector{-4, i})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, TileWall, tile) 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)
}
} }

View file

@ -7,7 +7,7 @@ import (
) )
func TestCommand_Move(t *testing.T) { func TestCommand_Move(t *testing.T) {
world := NewWorld() world := NewWorld(2, 4)
a, err := world.SpawnRover() a, err := world.SpawnRover()
assert.NoError(t, err) assert.NoError(t, err)
pos := Vector{ pos := Vector{

View file

@ -28,17 +28,20 @@ type World struct {
} }
// NewWorld creates a new world object // NewWorld creates a new world object
func NewWorld() *World { func NewWorld(size int, chunkSize int) *World {
return &World{ return &World{
Rovers: make(map[uuid.UUID]Rover), Rovers: make(map[uuid.UUID]Rover),
CommandQueue: make(map[uuid.UUID]CommandStream), 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 // SpawnWorld spawns a border at the edge of the world atlas
func (w *World) SpawnWorld() error { 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 // 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 // WarpRover sets an rovers position
func (w *World) WarpRover(id uuid.UUID, pos Vector) error { func (w *World) WarpRover(id uuid.UUID, pos Vector) error {
w.worldMutex.Lock() w.worldMutex.Lock()
@ -214,8 +231,8 @@ func (w *World) RadarFromRover(id uuid.UUID) ([]Tile, error) {
// Gather up all tiles within the range // Gather up all tiles within the range
var radar = make([]Tile, radarSpan*radarSpan) 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} q := Vector{i, j}
if tile, err := w.Atlas.GetTile(q); err != nil { if tile, err := w.Atlas.GetTile(q); err != nil {
@ -224,7 +241,8 @@ func (w *World) RadarFromRover(id uuid.UUID) ([]Tile, error) {
} else { } 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())
radar[relative.X+relative.Y*radarSpan] = tile index := relative.X + relative.Y*radarSpan
radar[index] = tile
} }
} }
} }

View file

@ -8,14 +8,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() world := NewWorld(4, 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() world := NewWorld(2, 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()
@ -30,7 +30,7 @@ func TestWorld_CreateRover(t *testing.T) {
} }
func TestWorld_RoverAttributes(t *testing.T) { func TestWorld_RoverAttributes(t *testing.T) {
world := NewWorld() world := NewWorld(2, 4)
a, err := world.SpawnRover() a, err := world.SpawnRover()
assert.NoError(t, err) assert.NoError(t, err)
@ -41,7 +41,7 @@ func TestWorld_RoverAttributes(t *testing.T) {
} }
func TestWorld_DestroyRover(t *testing.T) { func TestWorld_DestroyRover(t *testing.T) {
world := NewWorld() world := NewWorld(4, 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()
@ -59,7 +59,7 @@ func TestWorld_DestroyRover(t *testing.T) {
} }
func TestWorld_GetSetMovePosition(t *testing.T) { func TestWorld_GetSetMovePosition(t *testing.T) {
world := NewWorld() world := NewWorld(4, 4)
a, err := world.SpawnRover() a, err := world.SpawnRover()
assert.NoError(t, err) assert.NoError(t, err)
attribs, err := world.RoverAttributes(a) attribs, err := world.RoverAttributes(a)
@ -91,29 +91,52 @@ func TestWorld_GetSetMovePosition(t *testing.T) {
} }
func TestWorld_RadarFromRover(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() a, err := world.SpawnRover()
assert.NoError(t, err) assert.NoError(t, err)
b, err := world.SpawnRover() b, err := world.SpawnRover()
assert.NoError(t, err) assert.NoError(t, err)
// Get a's attributes // Set the rover range to a predictable value
attrib, err := world.RoverAttributes(a) attrib, err := world.RoverAttributes(a)
assert.NoError(t, err, "Failed to get rover attribs") 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 // Warp the rovers into position
bpos := Vector{-attrib.Range, -attrib.Range} bpos := Vector{-3, -3}
assert.NoError(t, world.WarpRover(a, Vector{0, 0}), "Failed to warp rover") 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(b, bpos), "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 := attrib.Range + attrib.Range + 1 fullRange := 4 + 4 + 1
assert.Equal(t, fullRange*fullRange, len(radar), "Radar returned wrong number of rovers") assert.Equal(t, fullRange*fullRange, len(radar), "Radar returned wrong length")
// bottom left should be a rover (we put one there with bpos) // It should look like:
assert.Equal(t, radar[0], TileRover, "Rover not found on radar in expected position") // ---------
// Centre should be rover // OOOOOOOO-
assert.Equal(t, radar[fullRange*fullRange/2], TileRover, "Rover not found on radar in expected position") // 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])
}
} }

View file

@ -140,15 +140,21 @@ func TestHandleRadar(t *testing.T) {
assert.NoError(t, err, "Error registering account") assert.NoError(t, err, "Error registering account")
// Spawn the rover rover for the 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) assert.NoError(t, err)
// Warp this rover to 0 // Warp this rover to 0,0
assert.NoError(t, s.world.WarpRover(id, game.Vector{})) assert.NoError(t, s.world.WarpRover(id, game.Vector{}))
// Set a tile to wall below this rover // Explicity set a few nearby tiles
wallPos := game.Vector{X: 0, Y: -1} wallPos1 := game.Vector{X: 0, Y: -1}
assert.NoError(t, s.world.Atlas.SetTile(wallPos, game.TileWall)) 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) request, _ := http.NewRequest(http.MethodGet, path.Join("/", a.Id.String(), "/radar"), nil)
response := httptest.NewRecorder() response := httptest.NewRecorder()
@ -163,7 +169,22 @@ func TestHandleRadar(t *testing.T) {
t.Errorf("got false for /radar: %s", status.Error) 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]) 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) { func TestHandleRover(t *testing.T) {

View file

@ -98,7 +98,7 @@ func NewServer(opts ...ServerOption) *Server {
// Create the accountant // Create the accountant
s.accountant = accounts.NewAccountant() s.accountant = accounts.NewAccountant()
s.world = game.NewWorld() s.world = game.NewWorld(4, 8) // TODO: Configure this
return s return s
} }