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)
} else {
// Print the radar
// Print out the radar
num := int(math.Sqrt(float64(len(response.Tiles))))
for i := 0; i < num; i++ {
for j := num - 1; j >= 0; j-- {
for j := num - 1; j >= 0; j-- {
for i := 0; i < num; i++ {
fmt.Printf("%d", response.Tiles[i+num*j])
}
fmt.Print("\n")

View file

@ -47,8 +47,8 @@ func NewAtlas(size int, chunkSize int) Atlas {
return a
}
// SpawnWorld spawns the current world
func (a *Atlas) SpawnWorld() error {
// SpawnRocks peppers the world with rocks
func (a *Atlas) SpawnRocks() error {
extent := a.ChunkSize * (a.Size / 2)
// Pepper the current world with rocks
@ -62,16 +62,23 @@ func (a *Atlas) SpawnWorld() error {
}
}
return nil
}
// SpawnWalls spawns the around the world
func (a *Atlas) SpawnWalls() error {
extent := a.ChunkSize * (a.Size / 2)
// Surround the atlas in walls
for i := -extent; i < extent; i++ {
if err := a.SetTile(Vector{i, extent - 1}, TileWall); err != nil {
if err := a.SetTile(Vector{i, extent - 1}, TileWall); err != nil { // N
return err
} else if a.SetTile(Vector{extent - 1, i}, TileWall); err != nil {
} else if a.SetTile(Vector{extent - 1, i}, TileWall); err != nil { // E
return err
} else if a.SetTile(Vector{-extent, i}, TileWall); err != nil {
} else if a.SetTile(Vector{i, -extent}, TileWall); err != nil { // S
return err
} else if a.SetTile(Vector{i, extent - 1}, TileWall); err != nil {
} else if a.SetTile(Vector{-extent, i}, TileWall); err != nil { // W
return err
}
}

View file

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

View file

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

View file

@ -28,17 +28,20 @@ type World struct {
}
// NewWorld creates a new world object
func NewWorld() *World {
func NewWorld(size int, chunkSize int) *World {
return &World{
Rovers: make(map[uuid.UUID]Rover),
CommandQueue: make(map[uuid.UUID]CommandStream),
Atlas: NewAtlas(4, 8), // TODO: Choose an appropriate world size
Atlas: NewAtlas(size, chunkSize), // TODO: Choose an appropriate world size
}
}
// SpawnWorld spawns a border at the edge of the world atlas
func (w *World) SpawnWorld() error {
return w.Atlas.SpawnWorld()
if err := w.Atlas.SpawnRocks(); err != nil {
return err
}
return w.Atlas.SpawnWalls()
}
// SpawnRover adds an rover to the game
@ -120,6 +123,20 @@ func (w *World) RoverAttributes(id uuid.UUID) (RoverAttributes, error) {
}
}
// SetRoverAttributes sets the attributes of a requested rover
func (w *World) SetRoverAttributes(id uuid.UUID, attributes RoverAttributes) error {
w.worldMutex.Lock()
defer w.worldMutex.Unlock()
if i, ok := w.Rovers[id]; ok {
i.Attributes = attributes
w.Rovers[id] = i
return nil
} else {
return fmt.Errorf("no rover matching id")
}
}
// WarpRover sets an rovers position
func (w *World) WarpRover(id uuid.UUID, pos Vector) error {
w.worldMutex.Lock()
@ -214,8 +231,8 @@ func (w *World) RadarFromRover(id uuid.UUID) ([]Tile, error) {
// Gather up all tiles within the range
var radar = make([]Tile, radarSpan*radarSpan)
for i := scanMin.X; i < scanMax.X; i++ {
for j := scanMin.Y; j < scanMax.Y; j++ {
for j := scanMin.Y; j <= scanMax.Y; j++ {
for i := scanMin.X; i <= scanMax.X; i++ {
q := Vector{i, j}
if tile, err := w.Atlas.GetTile(q); err != nil {
@ -224,7 +241,8 @@ func (w *World) RadarFromRover(id uuid.UUID) ([]Tile, error) {
} else {
// Get the position relative to the bottom left of the radar
relative := q.Added(radarMin.Negated())
radar[relative.X+relative.Y*radarSpan] = tile
index := relative.X + relative.Y*radarSpan
radar[index] = tile
}
}
}

View file

@ -8,14 +8,14 @@ import (
func TestNewWorld(t *testing.T) {
// Very basic for now, nothing to verify
world := NewWorld()
world := NewWorld(4, 4)
if world == nil {
t.Error("Failed to create world")
}
}
func TestWorld_CreateRover(t *testing.T) {
world := NewWorld()
world := NewWorld(2, 8)
a, err := world.SpawnRover()
assert.NoError(t, err)
b, err := world.SpawnRover()
@ -30,7 +30,7 @@ func TestWorld_CreateRover(t *testing.T) {
}
func TestWorld_RoverAttributes(t *testing.T) {
world := NewWorld()
world := NewWorld(2, 4)
a, err := world.SpawnRover()
assert.NoError(t, err)
@ -41,7 +41,7 @@ func TestWorld_RoverAttributes(t *testing.T) {
}
func TestWorld_DestroyRover(t *testing.T) {
world := NewWorld()
world := NewWorld(4, 1)
a, err := world.SpawnRover()
assert.NoError(t, err)
b, err := world.SpawnRover()
@ -59,7 +59,7 @@ func TestWorld_DestroyRover(t *testing.T) {
}
func TestWorld_GetSetMovePosition(t *testing.T) {
world := NewWorld()
world := NewWorld(4, 4)
a, err := world.SpawnRover()
assert.NoError(t, err)
attribs, err := world.RoverAttributes(a)
@ -91,29 +91,52 @@ func TestWorld_GetSetMovePosition(t *testing.T) {
}
func TestWorld_RadarFromRover(t *testing.T) {
world := NewWorld()
// Create world that should have visible walls on the radar
world := NewWorld(4, 2)
a, err := world.SpawnRover()
assert.NoError(t, err)
b, err := world.SpawnRover()
assert.NoError(t, err)
// Get a's attributes
// Set the rover range to a predictable value
attrib, err := world.RoverAttributes(a)
assert.NoError(t, err, "Failed to get rover attribs")
attrib.Range = 4 // Set the range to 4 so we can predict the radar fully
err = world.SetRoverAttributes(a, attrib)
assert.NoError(t, err, "Failed to set rover attribs")
// Warp the rovers so a can see b
bpos := Vector{-attrib.Range, -attrib.Range}
// Warp the rovers into position
bpos := Vector{-3, -3}
assert.NoError(t, world.WarpRover(a, Vector{0, 0}), "Failed to warp rover")
assert.NoError(t, world.WarpRover(b, bpos), "Failed to warp rover")
// Spawn the world wall
err = world.Atlas.SpawnWalls()
assert.NoError(t, err)
radar, err := world.RadarFromRover(a)
assert.NoError(t, err, "Failed to get radar from rover")
fullRange := attrib.Range + attrib.Range + 1
assert.Equal(t, fullRange*fullRange, len(radar), "Radar returned wrong number of rovers")
fullRange := 4 + 4 + 1
assert.Equal(t, fullRange*fullRange, len(radar), "Radar returned wrong length")
// bottom left should be a rover (we put one there with bpos)
assert.Equal(t, radar[0], TileRover, "Rover not found on radar in expected position")
// Centre should be rover
assert.Equal(t, radar[fullRange*fullRange/2], TileRover, "Rover not found on radar in expected position")
// It should look like:
// ---------
// OOOOOOOO-
// O------O-
// O------O-
// O---R--O-
// O------O-
// O------O-
// OR-----O-
// OOOOOOOO-
// Test all expected values
assert.Equal(t, TileRover, radar[1+fullRange])
assert.Equal(t, TileRover, radar[4+4*fullRange])
for i := 0; i < 8; i++ {
assert.Equal(t, TileWall, radar[i])
assert.Equal(t, TileWall, radar[i+(7*9)])
assert.Equal(t, TileWall, radar[i*9])
assert.Equal(t, TileWall, radar[(i*9)+7])
}
}

View file

@ -140,15 +140,21 @@ func TestHandleRadar(t *testing.T) {
assert.NoError(t, err, "Error registering account")
// Spawn the rover rover for the account
_, id, err := s.SpawnRoverForAccount(a.Id)
attrib, id, err := s.SpawnRoverForAccount(a.Id)
assert.NoError(t, err)
// Warp this rover to 0
// Warp this rover to 0,0
assert.NoError(t, s.world.WarpRover(id, game.Vector{}))
// Set a tile to wall below this rover
wallPos := game.Vector{X: 0, Y: -1}
assert.NoError(t, s.world.Atlas.SetTile(wallPos, game.TileWall))
// Explicity set a few nearby tiles
wallPos1 := game.Vector{X: 0, Y: -1}
wallPos2 := game.Vector{X: 1, Y: 1}
rockPos := game.Vector{X: 1, Y: 3}
emptyPos := game.Vector{X: -2, Y: -3}
assert.NoError(t, s.world.Atlas.SetTile(wallPos1, game.TileWall))
assert.NoError(t, s.world.Atlas.SetTile(wallPos2, game.TileWall))
assert.NoError(t, s.world.Atlas.SetTile(rockPos, game.TileRock))
assert.NoError(t, s.world.Atlas.SetTile(emptyPos, game.TileEmpty))
request, _ := http.NewRequest(http.MethodGet, path.Join("/", a.Id.String(), "/radar"), nil)
response := httptest.NewRecorder()
@ -163,7 +169,22 @@ func TestHandleRadar(t *testing.T) {
t.Errorf("got false for /radar: %s", status.Error)
}
scope := attrib.Range*2 + 1
radarOrigin := game.Vector{X: -attrib.Range, Y: -attrib.Range}
// Make sure the rover tile is correct
assert.Equal(t, game.TileRover, status.Tiles[len(status.Tiles)/2])
// Check our other tiles
wallPos1.Add(radarOrigin.Negated())
wallPos2.Add(radarOrigin.Negated())
rockPos.Add(radarOrigin.Negated())
emptyPos.Add(radarOrigin.Negated())
assert.Equal(t, game.TileWall, status.Tiles[wallPos1.X+wallPos1.Y*scope])
assert.Equal(t, game.TileWall, status.Tiles[wallPos2.X+wallPos2.Y*scope])
assert.Equal(t, game.TileRock, status.Tiles[rockPos.X+rockPos.Y*scope])
assert.Equal(t, game.TileEmpty, status.Tiles[emptyPos.X+emptyPos.Y*scope])
}
func TestHandleRover(t *testing.T) {

View file

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