Large refactor to properly implement radar
/radar now returns a set of non-empty tile blips
This commit is contained in:
parent
fc54775df9
commit
43648926ca
11 changed files with 182 additions and 50 deletions
|
@ -85,13 +85,13 @@ func (a *Atlas) SetTile(v Vector, tile Tile) error {
|
|||
// GetTile will return an individual tile
|
||||
func (a *Atlas) GetTile(v Vector) (Tile, error) {
|
||||
chunk := a.ToChunk(v)
|
||||
if chunk > len(a.Chunks) {
|
||||
if chunk >= len(a.Chunks) {
|
||||
return 0, fmt.Errorf("location outside of allocated atlas")
|
||||
}
|
||||
|
||||
local := a.ToChunkLocal(v)
|
||||
tileId := local.X + local.Y*a.ChunkSize
|
||||
if tileId > len(a.Chunks[chunk].Tiles) {
|
||||
if tileId >= len(a.Chunks[chunk].Tiles) {
|
||||
return 0, fmt.Errorf("location outside of allocated chunk")
|
||||
}
|
||||
|
||||
|
@ -131,6 +131,11 @@ func (a *Atlas) ChunkOrigin(chunk int) Vector {
|
|||
return v.Multiplied(a.ChunkSize)
|
||||
}
|
||||
|
||||
// GetWorldExtent gets the extent of the world
|
||||
func (a *Atlas) GetWorldExtent() int {
|
||||
return (a.Size / 2) * a.ChunkSize
|
||||
}
|
||||
|
||||
// Grow will return a grown copy of the current atlas
|
||||
func (a *Atlas) Grow(size int) error {
|
||||
if size%2 != 0 {
|
||||
|
|
|
@ -8,7 +8,8 @@ import (
|
|||
|
||||
func TestCommand_Move(t *testing.T) {
|
||||
world := NewWorld()
|
||||
a := world.SpawnRover()
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
pos := Vector{
|
||||
X: 1.0,
|
||||
Y: 2.0,
|
||||
|
|
|
@ -27,6 +27,22 @@ func Pmod(x, d int) int {
|
|||
}
|
||||
}
|
||||
|
||||
// Max returns the highest int
|
||||
func Max(x int, y int) int {
|
||||
if x < y {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Min returns the lowest int
|
||||
func Min(x int, y int) int {
|
||||
if x > y {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Vector desribes a 3D vector
|
||||
type Vector struct {
|
||||
X int `json:"x"`
|
||||
|
|
|
@ -7,4 +7,6 @@ const (
|
|||
TileEmpty = Tile(0)
|
||||
|
||||
TileWall = Tile(1)
|
||||
|
||||
TileRover = Tile(2)
|
||||
)
|
||||
|
|
|
@ -42,7 +42,7 @@ func (w *World) SpawnWorldBorder() error {
|
|||
}
|
||||
|
||||
// SpawnRover adds an rover to the game
|
||||
func (w *World) SpawnRover() uuid.UUID {
|
||||
func (w *World) SpawnRover() (uuid.UUID, error) {
|
||||
w.worldMutex.Lock()
|
||||
defer w.worldMutex.Unlock()
|
||||
|
||||
|
@ -52,7 +52,7 @@ func (w *World) SpawnRover() uuid.UUID {
|
|||
Attributes: RoverAttributes{
|
||||
|
||||
Speed: 1.0,
|
||||
Range: 20.0,
|
||||
Range: 5.0,
|
||||
|
||||
// Set the name randomly
|
||||
Name: babble.NewBabbler().Babble(),
|
||||
|
@ -65,12 +65,29 @@ func (w *World) SpawnRover() uuid.UUID {
|
|||
w.Atlas.ChunkSize - (rand.Int() % (w.Atlas.ChunkSize * 2)),
|
||||
}
|
||||
|
||||
// TODO: Verify no blockages in this area
|
||||
// Seach until we error (run out of world)
|
||||
for {
|
||||
if tile, err := w.Atlas.GetTile(rover.Attributes.Pos); err != nil {
|
||||
return uuid.Nil, err
|
||||
} else {
|
||||
if tile == TileEmpty {
|
||||
break
|
||||
} else {
|
||||
// Try and spawn to the east of the blockage
|
||||
rover.Attributes.Pos.Add(Vector{1, 0})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the world tile to a rover
|
||||
if err := w.Atlas.SetTile(rover.Attributes.Pos, TileRover); err != nil {
|
||||
return uuid.Nil, err
|
||||
}
|
||||
|
||||
// Append the rover to the list
|
||||
w.Rovers[rover.Id] = rover
|
||||
|
||||
return rover.Id
|
||||
return rover.Id, nil
|
||||
}
|
||||
|
||||
// Removes an rover from the game
|
||||
|
@ -78,7 +95,11 @@ func (w *World) DestroyRover(id uuid.UUID) error {
|
|||
w.worldMutex.Lock()
|
||||
defer w.worldMutex.Unlock()
|
||||
|
||||
if _, ok := w.Rovers[id]; ok {
|
||||
if i, ok := w.Rovers[id]; ok {
|
||||
// Clear the tile
|
||||
if err := w.Atlas.SetTile(i.Attributes.Pos, TileEmpty); err != nil {
|
||||
return fmt.Errorf("coudln't clear old rover tile: %s", err)
|
||||
}
|
||||
delete(w.Rovers, id)
|
||||
} else {
|
||||
return fmt.Errorf("no rover matching id")
|
||||
|
@ -104,6 +125,14 @@ func (w *World) WarpRover(id uuid.UUID, pos Vector) error {
|
|||
defer w.worldMutex.Unlock()
|
||||
|
||||
if i, ok := w.Rovers[id]; ok {
|
||||
// Update the world tile
|
||||
// TODO: Make this (and other things) transactional
|
||||
if err := w.Atlas.SetTile(pos, TileRover); err != nil {
|
||||
return fmt.Errorf("coudln't set rover tile: %s", err)
|
||||
} else if err := w.Atlas.SetTile(i.Attributes.Pos, TileEmpty); err != nil {
|
||||
return fmt.Errorf("coudln't clear old rover tile: %s", err)
|
||||
}
|
||||
|
||||
i.Attributes.Pos = pos
|
||||
w.Rovers[id] = i
|
||||
return nil
|
||||
|
@ -129,8 +158,16 @@ func (w *World) MoveRover(id uuid.UUID, bearing Direction) (RoverAttributes, err
|
|||
|
||||
// Get the tile and verify it's empty
|
||||
if tile, err := w.Atlas.GetTile(newPos); err != nil {
|
||||
return i.Attributes, fmt.Errorf("couldn't get tile for new position")
|
||||
return i.Attributes, fmt.Errorf("couldn't get tile for new position: %s", err)
|
||||
} else if tile == TileEmpty {
|
||||
// Set the world tiles
|
||||
// TODO: Make this (and other things) transactional
|
||||
if err := w.Atlas.SetTile(newPos, TileRover); err != nil {
|
||||
return i.Attributes, fmt.Errorf("coudln't set rover tile: %s", err)
|
||||
} else if err := w.Atlas.SetTile(i.Attributes.Pos, TileEmpty); err != nil {
|
||||
return i.Attributes, fmt.Errorf("coudln't clear old rover tile: %s", err)
|
||||
}
|
||||
|
||||
// Perform the move
|
||||
i.Attributes.Pos = newPos
|
||||
w.Rovers[id] = i
|
||||
|
@ -142,30 +179,54 @@ func (w *World) MoveRover(id uuid.UUID, bearing Direction) (RoverAttributes, err
|
|||
}
|
||||
}
|
||||
|
||||
// RadarDescription describes what a rover can see
|
||||
type RadarDescription struct {
|
||||
// Rovers is the set of rovers that this radar can see
|
||||
Rovers []Vector `json:"rovers"`
|
||||
// RadarBlip represents a single blip on the radar
|
||||
type RadarBlip struct {
|
||||
Position Vector `json:"position"`
|
||||
Tile Tile `json:"tile"`
|
||||
}
|
||||
|
||||
// RadarFromRover can be used to query what a rover can currently see
|
||||
func (w *World) RadarFromRover(id uuid.UUID) (RadarDescription, error) {
|
||||
func (w *World) RadarFromRover(id uuid.UUID) ([]RadarBlip, error) {
|
||||
w.worldMutex.RLock()
|
||||
defer w.worldMutex.RUnlock()
|
||||
|
||||
if r1, ok := w.Rovers[id]; ok {
|
||||
var desc RadarDescription
|
||||
if r, ok := w.Rovers[id]; ok {
|
||||
var blips []RadarBlip
|
||||
|
||||
// Gather nearby rovers within the range
|
||||
for _, r2 := range w.Rovers {
|
||||
if r1.Id != r2.Id && r1.Attributes.Pos.Distance(r2.Attributes.Pos) < float64(r1.Attributes.Range) {
|
||||
desc.Rovers = append(desc.Rovers, r2.Attributes.Pos)
|
||||
extent := w.Atlas.GetWorldExtent()
|
||||
|
||||
// Get min and max extents to query
|
||||
min := Vector{
|
||||
Max(-extent, r.Attributes.Pos.X-r.Attributes.Range),
|
||||
Max(-extent, r.Attributes.Pos.Y-r.Attributes.Range),
|
||||
}
|
||||
max := Vector{
|
||||
Min(extent-1, r.Attributes.Pos.X+r.Attributes.Range),
|
||||
Min(extent-1, r.Attributes.Pos.Y+r.Attributes.Range),
|
||||
}
|
||||
|
||||
// Gather up all tiles within the range
|
||||
for i := min.X; i < max.X; i++ {
|
||||
for j := min.Y; j < max.Y; j++ {
|
||||
|
||||
// Skip this rover
|
||||
q := Vector{i, j}
|
||||
if q == r.Attributes.Pos {
|
||||
continue
|
||||
}
|
||||
|
||||
if tile, err := w.Atlas.GetTile(q); err != nil {
|
||||
return blips, fmt.Errorf("failed to query tile: %s", err)
|
||||
|
||||
} else if tile != TileEmpty {
|
||||
blips = append(blips, RadarBlip{Position: q, Tile: tile})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return desc, nil
|
||||
return blips, nil
|
||||
} else {
|
||||
return RadarDescription{}, fmt.Errorf("no rover matching id")
|
||||
return nil, fmt.Errorf("no rover matching id")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,8 +16,10 @@ func TestNewWorld(t *testing.T) {
|
|||
|
||||
func TestWorld_CreateRover(t *testing.T) {
|
||||
world := NewWorld()
|
||||
a := world.SpawnRover()
|
||||
b := world.SpawnRover()
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
b, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Basic duplicate check
|
||||
if a == b {
|
||||
|
@ -29,7 +31,8 @@ func TestWorld_CreateRover(t *testing.T) {
|
|||
|
||||
func TestWorld_RoverAttributes(t *testing.T) {
|
||||
world := NewWorld()
|
||||
a := world.SpawnRover()
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
attribs, err := world.RoverAttributes(a)
|
||||
assert.NoError(t, err, "Failed to get rover attribs")
|
||||
|
@ -39,10 +42,12 @@ func TestWorld_RoverAttributes(t *testing.T) {
|
|||
|
||||
func TestWorld_DestroyRover(t *testing.T) {
|
||||
world := NewWorld()
|
||||
a := world.SpawnRover()
|
||||
b := world.SpawnRover()
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
b, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err := world.DestroyRover(a)
|
||||
err = world.DestroyRover(a)
|
||||
assert.NoError(t, err, "Error returned from rover destroy")
|
||||
|
||||
// Basic duplicate check
|
||||
|
@ -55,7 +60,8 @@ func TestWorld_DestroyRover(t *testing.T) {
|
|||
|
||||
func TestWorld_GetSetMovePosition(t *testing.T) {
|
||||
world := NewWorld()
|
||||
a := world.SpawnRover()
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
attribs, err := world.RoverAttributes(a)
|
||||
assert.NoError(t, err, "Failed to get rover attribs")
|
||||
|
||||
|
@ -86,22 +92,34 @@ func TestWorld_GetSetMovePosition(t *testing.T) {
|
|||
|
||||
func TestWorld_RadarFromRover(t *testing.T) {
|
||||
world := NewWorld()
|
||||
a := world.SpawnRover()
|
||||
b := world.SpawnRover()
|
||||
c := world.SpawnRover()
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
b, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
c, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get a's attributes
|
||||
attrib, err := world.RoverAttributes(a)
|
||||
assert.NoError(t, err, "Failed to get rover attribs")
|
||||
|
||||
// Warp the rovers so a can see b but not c
|
||||
bpos := Vector{attrib.Range - 1, 0}
|
||||
cpos := Vector{attrib.Range + 1, 0}
|
||||
assert.NoError(t, world.WarpRover(a, Vector{0, 0}), "Failed to warp rover")
|
||||
assert.NoError(t, world.WarpRover(b, Vector{attrib.Range - 1, 0}), "Failed to warp rover")
|
||||
assert.NoError(t, world.WarpRover(c, Vector{attrib.Range + 1, 0}), "Failed to warp rover")
|
||||
assert.NoError(t, world.WarpRover(b, bpos), "Failed to warp rover")
|
||||
assert.NoError(t, world.WarpRover(c, cpos), "Failed to warp rover")
|
||||
|
||||
radar, err := world.RadarFromRover(a)
|
||||
assert.NoError(t, err, "Failed to get radar from rover")
|
||||
assert.Equal(t, 1, len(radar.Rovers), "Radar returned wrong number of rovers")
|
||||
assert.Equal(t, Vector{attrib.Range - 1, 0}, radar.Rovers[0], "Rover on radar in wrong position")
|
||||
assert.Equal(t, 1, len(radar), "Radar returned wrong number of rovers")
|
||||
|
||||
found := false
|
||||
for _, blip := range radar {
|
||||
if blip.Position == bpos && blip.Tile == TileRover {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "Rover not found on radar in expected position")
|
||||
|
||||
}
|
||||
|
|
|
@ -104,8 +104,8 @@ type RadarResponse struct {
|
|||
Success bool `json:"success"`
|
||||
Error string `json:"error,omitempty"`
|
||||
|
||||
// The set of positions for nearby rovers
|
||||
Rovers []game.Vector `json:"rovers"`
|
||||
// The set of positions for nearby non-empty tiles
|
||||
Blips []game.RadarBlip `json:"blips"`
|
||||
}
|
||||
|
||||
// ================
|
||||
|
|
|
@ -197,7 +197,7 @@ func HandleRadar(s *Server, vars map[string]string, b io.ReadCloser, w io.Writer
|
|||
|
||||
} else {
|
||||
fmt.Printf("Responded with radar\taccount:%s\tradar:%+v\n", id, radar)
|
||||
response.Rovers = radar.Rovers
|
||||
response.Blips = radar
|
||||
response.Success = true
|
||||
}
|
||||
|
||||
|
|
|
@ -140,7 +140,22 @@ func TestHandleRadar(t *testing.T) {
|
|||
assert.NoError(t, err, "Error registering account")
|
||||
|
||||
// Spawn the rover rover for the account
|
||||
_, _, err = s.SpawnRoverForAccount(a.Id)
|
||||
_, id, err := s.SpawnRoverForAccount(a.Id)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Warp this rover to 0
|
||||
assert.NoError(t, s.world.WarpRover(id, game.Vector{}))
|
||||
|
||||
// Spawn another rover
|
||||
id, err = s.world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
// Warp this rover to just above the other one
|
||||
roverPos := game.Vector{X: 0, Y: 1}
|
||||
assert.NoError(t, s.world.WarpRover(id, roverPos))
|
||||
|
||||
// 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))
|
||||
|
||||
request, _ := http.NewRequest(http.MethodGet, path.Join("/", a.Id.String(), "/radar"), nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
@ -155,7 +170,18 @@ func TestHandleRadar(t *testing.T) {
|
|||
t.Errorf("got false for /radar: %s", status.Error)
|
||||
}
|
||||
|
||||
// TODO: Verify the radar information
|
||||
foundWall := false
|
||||
foundRover := false
|
||||
for _, b := range status.Blips {
|
||||
if b.Position == wallPos && b.Tile == game.TileWall {
|
||||
foundWall = true
|
||||
} else if b.Position == roverPos && b.Tile == game.TileRover {
|
||||
foundRover = true
|
||||
}
|
||||
}
|
||||
assert.True(t, foundWall)
|
||||
assert.True(t, foundRover)
|
||||
|
||||
}
|
||||
|
||||
func TestHandleRover(t *testing.T) {
|
||||
|
@ -164,8 +190,9 @@ func TestHandleRover(t *testing.T) {
|
|||
a, err := s.accountant.RegisterAccount("test")
|
||||
assert.NoError(t, err, "Error registering account")
|
||||
|
||||
// Spawn the rover rover for the account
|
||||
_, _, err = s.SpawnRoverForAccount(a.Id)
|
||||
// Spawn one rover for the account
|
||||
attribs, _, err := s.SpawnRoverForAccount(a.Id)
|
||||
assert.NoError(t, err)
|
||||
|
||||
request, _ := http.NewRequest(http.MethodGet, path.Join("/", a.Id.String(), "/rover"), nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
@ -178,7 +205,7 @@ func TestHandleRover(t *testing.T) {
|
|||
|
||||
if status.Success != true {
|
||||
t.Errorf("got false for /rover: %s", status.Error)
|
||||
} else if attribs != status.Attributes {
|
||||
t.Errorf("Missmatched attributes: %+v, !=%+v", attribs, status.Attributes)
|
||||
}
|
||||
|
||||
// TODO: Verify the rover information
|
||||
}
|
||||
|
|
|
@ -269,7 +269,7 @@ func (s *Server) wrapHandler(method string, handler Handler) func(w http.Respons
|
|||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
} else if err := json.NewEncoder(w).Encode(val); err != nil {
|
||||
fmt.Printf("Failed to encode return to json: %s", err)
|
||||
fmt.Printf("Failed to encode reply to json: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
} else {
|
||||
|
@ -280,9 +280,11 @@ func (s *Server) wrapHandler(method string, handler Handler) func(w http.Respons
|
|||
|
||||
// SpawnRoverForAccount spawns the rover rover for an account
|
||||
func (s *Server) SpawnRoverForAccount(accountid uuid.UUID) (game.RoverAttributes, uuid.UUID, error) {
|
||||
inst := s.world.SpawnRover()
|
||||
if attribs, err := s.world.RoverAttributes(inst); err != nil {
|
||||
return game.RoverAttributes{}, uuid.UUID{}, fmt.Errorf("No attributes found for created rover")
|
||||
if inst, err := s.world.SpawnRover(); err != nil {
|
||||
return game.RoverAttributes{}, uuid.UUID{}, err
|
||||
|
||||
} else if attribs, err := s.world.RoverAttributes(inst); err != nil {
|
||||
return game.RoverAttributes{}, uuid.UUID{}, fmt.Errorf("No attributes found for created rover: %s", err)
|
||||
|
||||
} else {
|
||||
if err := s.accountant.AssignRover(accountid, inst); err != nil {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue