diff --git a/pkg/bearing/bearing.go b/pkg/bearing/bearing.go new file mode 100644 index 0000000..90e771d --- /dev/null +++ b/pkg/bearing/bearing.go @@ -0,0 +1,76 @@ +package bearing + +import ( + "fmt" + "strings" + + "github.com/mdiluz/rove/pkg/vector" +) + +// Direction describes a compass direction +type Direction int + +const ( + North Direction = iota + NorthEast + East + SouthEast + South + SouthWest + West + NorthWest +) + +// DirectionString simply describes the strings associated with a direction +type DirectionString struct { + Long string + Short string +} + +// DirectionStrings is the set of strings for each direction +var DirectionStrings = []DirectionString{ + {"North", "N"}, + {"NorthEast", "NE"}, + {"East", "E"}, + {"SouthEast", "SE"}, + {"South", "S"}, + {"SouthWest", "SW"}, + {"West", "W"}, + {"NorthWest", "NW"}, +} + +// String converts a Direction to a String +func (d Direction) String() string { + return DirectionStrings[d].Long +} + +// ShortString converts a Direction to a short string version +func (d Direction) ShortString() string { + return DirectionStrings[d].Short +} + +// DirectionFromString gets the Direction from a string +func DirectionFromString(s string) (Direction, error) { + for i, d := range DirectionStrings { + if strings.ToLower(d.Long) == strings.ToLower(s) || strings.ToLower(d.Short) == strings.ToLower(s) { + return Direction(i), nil + } + } + return -1, fmt.Errorf("Unknown direction: %s", s) +} + +var DirectionVectors = []vector.Vector{ + {X: 0, Y: 1}, // N + {X: 1, Y: 1}, // NE + {X: 1, Y: 0}, // E + {X: 1, Y: -1}, // SE + {X: 0, Y: -1}, // S + {X: -1, Y: 1}, // SW + {X: -1, Y: 0}, // W + {X: -1, Y: 1}, // NW +} + +// Vector converts a Direction to a Vector +func (d Direction) Vector() vector.Vector { + return DirectionVectors[d] +} diff --git a/pkg/bearing/bearing_test.go b/pkg/bearing/bearing_test.go new file mode 100644 index 0000000..d9dc8e9 --- /dev/null +++ b/pkg/bearing/bearing_test.go @@ -0,0 +1,32 @@ +package bearing + +import ( + "testing" + + "github.com/mdiluz/rove/pkg/vector" + "github.com/stretchr/testify/assert" +) + +func TestDirection(t *testing.T) { + dir := North + + assert.Equal(t, "North", dir.String()) + assert.Equal(t, "N", dir.ShortString()) + assert.Equal(t, vector.Vector{X: 0, Y: 1}, dir.Vector()) + + dir, err := DirectionFromString("N") + assert.NoError(t, err) + assert.Equal(t, North, dir) + + dir, err = DirectionFromString("n") + assert.NoError(t, err) + assert.Equal(t, North, dir) + + dir, err = DirectionFromString("north") + assert.NoError(t, err) + assert.Equal(t, North, dir) + + dir, err = DirectionFromString("NorthWest") + assert.NoError(t, err) + assert.Equal(t, NorthWest, dir) +} diff --git a/pkg/game/atlas.go b/pkg/game/atlas.go index be2b8fe..7e0c855 100644 --- a/pkg/game/atlas.go +++ b/pkg/game/atlas.go @@ -4,6 +4,9 @@ import ( "fmt" "log" "math/rand" + + "github.com/mdiluz/rove/pkg/maths" + "github.com/mdiluz/rove/pkg/vector" ) // Chunk represents a fixed square grid of tiles @@ -55,7 +58,7 @@ func (a *Atlas) SpawnRocks() error { for i := -extent; i < extent; i++ { for j := -extent; j < extent; j++ { if rand.Intn(16) == 0 { - if err := a.SetTile(Vector{i, j}, TileRock); err != nil { + if err := a.SetTile(vector.Vector{X: i, Y: j}, TileRock); err != nil { return err } } @@ -72,13 +75,13 @@ func (a *Atlas) SpawnWalls() error { // Surround the atlas in walls for i := -extent; i < extent; i++ { - if err := a.SetTile(Vector{i, extent - 1}, TileWall); err != nil { // N + if err := a.SetTile(vector.Vector{X: i, Y: extent - 1}, TileWall); err != nil { // N return err - } else if a.SetTile(Vector{extent - 1, i}, TileWall); err != nil { // E + } else if a.SetTile(vector.Vector{X: extent - 1, Y: i}, TileWall); err != nil { // E return err - } else if a.SetTile(Vector{i, -extent}, TileWall); err != nil { // S + } else if a.SetTile(vector.Vector{X: i, Y: -extent}, TileWall); err != nil { // S return err - } else if a.SetTile(Vector{-extent, i}, TileWall); err != nil { // W + } else if a.SetTile(vector.Vector{X: -extent, Y: i}, TileWall); err != nil { // W return err } } @@ -87,7 +90,7 @@ func (a *Atlas) SpawnWalls() error { } // SetTile sets an individual tile's kind -func (a *Atlas) SetTile(v Vector, tile Tile) error { +func (a *Atlas) SetTile(v vector.Vector, tile Tile) error { chunk := a.ToChunk(v) if chunk >= len(a.Chunks) { return fmt.Errorf("location outside of allocated atlas") @@ -103,7 +106,7 @@ func (a *Atlas) SetTile(v Vector, tile Tile) error { } // GetTile will return an individual tile -func (a *Atlas) GetTile(v Vector) (Tile, error) { +func (a *Atlas) GetTile(v vector.Vector) (Tile, error) { chunk := a.ToChunk(v) if chunk >= len(a.Chunks) { return 0, fmt.Errorf("location outside of allocated atlas") @@ -119,32 +122,32 @@ func (a *Atlas) GetTile(v Vector) (Tile, error) { } // ToChunkLocal gets a chunk local coordinate for a tile -func (a *Atlas) ToChunkLocal(v Vector) Vector { - return Vector{Pmod(v.X, a.ChunkSize), Pmod(v.Y, a.ChunkSize)} +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)} } // GetChunkLocal gets a chunk local coordinate for a tile -func (a *Atlas) ToWorld(local Vector, chunk int) Vector { +func (a *Atlas) ToWorld(local vector.Vector, chunk int) vector.Vector { return a.ChunkOrigin(chunk).Added(local) } // GetChunkID gets the chunk ID for a position in the world -func (a *Atlas) ToChunk(v Vector) int { +func (a *Atlas) ToChunk(v vector.Vector) int { local := a.ToChunkLocal(v) // Get the chunk origin itself origin := v.Added(local.Negated()) // Divided it by the number of chunks origin = origin.Divided(a.ChunkSize) // Shift it by our size (our origin is in the middle) - origin = origin.Added(Vector{a.Size / 2, a.Size / 2}) + origin = origin.Added(vector.Vector{X: a.Size / 2, Y: a.Size / 2}) // Get the ID based on the final values return (a.Size * origin.Y) + origin.X } // ChunkOrigin gets the chunk origin for a given chunk index -func (a *Atlas) ChunkOrigin(chunk int) Vector { - v := Vector{ - X: Pmod(chunk, a.Size) - (a.Size / 2), +func (a *Atlas) ChunkOrigin(chunk int) vector.Vector { + v := vector.Vector{ + X: maths.Pmod(chunk, a.Size) - (a.Size / 2), Y: (chunk / a.Size) - (a.Size / 2), } @@ -152,14 +155,14 @@ func (a *Atlas) ChunkOrigin(chunk int) Vector { } // GetWorldExtent gets the min and max valid coordinates of world -func (a *Atlas) GetWorldExtents() (min Vector, max Vector) { - min = Vector{ - -(a.Size / 2) * a.ChunkSize, - -(a.Size / 2) * a.ChunkSize, +func (a *Atlas) GetWorldExtents() (min vector.Vector, max vector.Vector) { + min = vector.Vector{ + X: -(a.Size / 2) * a.ChunkSize, + Y: -(a.Size / 2) * a.ChunkSize, } - max = Vector{ - -min.X - 1, - -min.Y - 1, + max = vector.Vector{ + X: -min.X - 1, + Y: -min.Y - 1, } return } diff --git a/pkg/game/atlas_test.go b/pkg/game/atlas_test.go index 7256061..7ee1034 100644 --- a/pkg/game/atlas_test.go +++ b/pkg/game/atlas_test.go @@ -3,6 +3,7 @@ package game import ( "testing" + "github.com/mdiluz/rove/pkg/vector" "github.com/stretchr/testify/assert" ) @@ -28,13 +29,13 @@ func TestAtlas_ToChunk(t *testing.T) { // Tiles should look like: 2 | 3 // ----- // 0 | 1 - tile := a.ToChunk(Vector{0, 0}) + tile := a.ToChunk(vector.Vector{X: 0, Y: 0}) assert.Equal(t, 3, tile) - tile = a.ToChunk(Vector{0, -1}) + tile = a.ToChunk(vector.Vector{X: 0, Y: -1}) assert.Equal(t, 1, tile) - tile = a.ToChunk(Vector{-1, -1}) + tile = a.ToChunk(vector.Vector{X: -1, Y: -1}) assert.Equal(t, 0, tile) - tile = a.ToChunk(Vector{-1, 0}) + tile = a.ToChunk(vector.Vector{X: -1, Y: 0}) assert.Equal(t, 2, tile) a = NewAtlas(2, 2) @@ -43,13 +44,13 @@ func TestAtlas_ToChunk(t *testing.T) { // 2 | 3 // ----- // 0 | 1 - tile = a.ToChunk(Vector{1, 1}) + tile = a.ToChunk(vector.Vector{X: 1, Y: 1}) assert.Equal(t, 3, tile) - tile = a.ToChunk(Vector{1, -2}) + tile = a.ToChunk(vector.Vector{X: 1, Y: -2}) assert.Equal(t, 1, tile) - tile = a.ToChunk(Vector{-2, -2}) + tile = a.ToChunk(vector.Vector{X: -2, Y: -2}) assert.Equal(t, 0, tile) - tile = a.ToChunk(Vector{-2, 1}) + tile = a.ToChunk(vector.Vector{X: -2, Y: 1}) assert.Equal(t, 2, tile) a = NewAtlas(4, 2) @@ -62,13 +63,13 @@ func TestAtlas_ToChunk(t *testing.T) { // 4 | 5 || 6 | 7 // ---------------- // 0 | 1 || 2 | 3 - tile = a.ToChunk(Vector{1, 3}) + tile = a.ToChunk(vector.Vector{X: 1, Y: 3}) assert.Equal(t, 14, tile) - tile = a.ToChunk(Vector{1, -3}) + tile = a.ToChunk(vector.Vector{X: 1, Y: -3}) assert.Equal(t, 2, tile) - tile = a.ToChunk(Vector{-1, -1}) + tile = a.ToChunk(vector.Vector{X: -1, Y: -1}) assert.Equal(t, 5, tile) - tile = a.ToChunk(Vector{-2, 2}) + tile = a.ToChunk(vector.Vector{X: -2, Y: 2}) assert.Equal(t, 13, tile) } @@ -77,14 +78,14 @@ func TestAtlas_GetSetTile(t *testing.T) { assert.NotNil(t, a) // Set the origin tile to 1 and test it - assert.NoError(t, a.SetTile(Vector{0, 0}, 1)) - tile, err := a.GetTile(Vector{0, 0}) + assert.NoError(t, a.SetTile(vector.Vector{X: 0, Y: 0}, 1)) + tile, err := a.GetTile(vector.Vector{X: 0, Y: 0}) assert.NoError(t, err) assert.Equal(t, Tile(1), tile) // Set another tile to 1 and test it - assert.NoError(t, a.SetTile(Vector{5, -2}, 2)) - tile, err = a.GetTile(Vector{5, -2}) + assert.NoError(t, a.SetTile(vector.Vector{X: 5, Y: -2}, 2)) + tile, err = a.GetTile(vector.Vector{X: 5, Y: -2}) assert.NoError(t, err) assert.Equal(t, Tile(2), tile) } @@ -96,24 +97,24 @@ func TestAtlas_Grown(t *testing.T) { assert.Equal(t, 4, len(a.Chunks)) // Set a few tiles to values - assert.NoError(t, a.SetTile(Vector{0, 0}, 1)) - assert.NoError(t, a.SetTile(Vector{-1, -1}, 2)) - assert.NoError(t, a.SetTile(Vector{1, -2}, 3)) + assert.NoError(t, a.SetTile(vector.Vector{X: 0, Y: 0}, 1)) + assert.NoError(t, a.SetTile(vector.Vector{X: -1, Y: -1}, 2)) + assert.NoError(t, a.SetTile(vector.Vector{X: 1, Y: -2}, 3)) // Grow once to just double it err := a.Grow(4) assert.NoError(t, err) assert.Equal(t, 16, len(a.Chunks)) - tile, err := a.GetTile(Vector{0, 0}) + tile, err := a.GetTile(vector.Vector{X: 0, Y: 0}) assert.NoError(t, err) assert.Equal(t, Tile(1), tile) - tile, err = a.GetTile(Vector{-1, -1}) + tile, err = a.GetTile(vector.Vector{X: -1, Y: -1}) assert.NoError(t, err) assert.Equal(t, Tile(2), tile) - tile, err = a.GetTile(Vector{1, -2}) + tile, err = a.GetTile(vector.Vector{X: 1, Y: -2}) assert.NoError(t, err) assert.Equal(t, Tile(3), tile) @@ -122,15 +123,15 @@ func TestAtlas_Grown(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 100, len(a.Chunks)) - tile, err = a.GetTile(Vector{0, 0}) + tile, err = a.GetTile(vector.Vector{X: 0, Y: 0}) assert.NoError(t, err) assert.Equal(t, Tile(1), tile) - tile, err = a.GetTile(Vector{-1, -1}) + tile, err = a.GetTile(vector.Vector{X: -1, Y: -1}) assert.NoError(t, err) assert.Equal(t, Tile(2), tile) - tile, err = a.GetTile(Vector{1, -2}) + tile, err = a.GetTile(vector.Vector{X: 1, Y: -2}) assert.NoError(t, err) assert.Equal(t, Tile(3), tile) } @@ -143,25 +144,25 @@ func TestAtlas_SpawnWorld(t *testing.T) { assert.NoError(t, a.SpawnWalls()) for i := -4; i < 4; i++ { - tile, err := a.GetTile(Vector{i, -4}) + tile, err := a.GetTile(vector.Vector{X: i, Y: -4}) assert.NoError(t, err) assert.Equal(t, TileWall, tile) } for i := -4; i < 4; i++ { - tile, err := a.GetTile(Vector{-4, i}) + tile, err := a.GetTile(vector.Vector{X: -4, Y: i}) assert.NoError(t, err) assert.Equal(t, TileWall, tile) } for i := -4; i < 4; i++ { - tile, err := a.GetTile(Vector{3, i}) + tile, err := a.GetTile(vector.Vector{X: 3, Y: i}) assert.NoError(t, err) assert.Equal(t, TileWall, tile) } for i := -4; i < 4; i++ { - tile, err := a.GetTile(Vector{i, 3}) + tile, err := a.GetTile(vector.Vector{X: i, Y: 3}) assert.NoError(t, err) assert.Equal(t, TileWall, tile) } diff --git a/pkg/game/command_test.go b/pkg/game/command_test.go index 303d9dc..ebc528c 100644 --- a/pkg/game/command_test.go +++ b/pkg/game/command_test.go @@ -3,6 +3,8 @@ package game import ( "testing" + "github.com/mdiluz/rove/pkg/bearing" + "github.com/mdiluz/rove/pkg/vector" "github.com/stretchr/testify/assert" ) @@ -10,7 +12,7 @@ func TestCommand_Move(t *testing.T) { world := NewWorld(2, 8) a, err := world.SpawnRover() assert.NoError(t, err) - pos := Vector{ + pos := vector.Vector{ X: 1.0, Y: 2.0, } @@ -21,7 +23,7 @@ func TestCommand_Move(t *testing.T) { err = world.WarpRover(a, pos) assert.NoError(t, err, "Failed to set position for rover") - bearing := North + bearing := bearing.North duration := 1 // Try the move command moveCommand := Command{Command: CommandMove, Bearing: bearing.String(), Duration: duration} @@ -32,6 +34,6 @@ func TestCommand_Move(t *testing.T) { newatributes, err := world.RoverAttributes(a) assert.NoError(t, err, "Failed to set position for rover") - pos.Add(Vector{0.0, duration * attribs.Speed}) // We should have moved duration*speed north + pos.Add(vector.Vector{X: 0.0, Y: duration * attribs.Speed}) // We should have moved duration*speed north assert.Equal(t, pos, newatributes.Pos, "Failed to correctly set position for rover") } diff --git a/pkg/game/math.go b/pkg/game/math.go deleted file mode 100644 index 059e4ad..0000000 --- a/pkg/game/math.go +++ /dev/null @@ -1,158 +0,0 @@ -package game - -import ( - "fmt" - "math" - "strings" -) - -// TODO: Pull this out into math package and get more test coverage - -// Abs gets the absolute value of an int -func Abs(x int) int { - if x < 0 { - return -x - } - return x -} - -// pmod is a mositive modulo -// golang's % is a "remainder" function si misbehaves for negative modulus inputs -func Pmod(x, d int) int { - x = x % d - if x >= 0 { - return x - } else if d < 0 { - return x - d - } else { - return x + d - } -} - -// 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"` - Y int `json:"y"` -} - -// Add adds one vector to another -func (v *Vector) Add(v2 Vector) { - v.X += v2.X - v.Y += v2.Y -} - -// Added calculates a new vector -func (v Vector) Added(v2 Vector) Vector { - v.Add(v2) - return v -} - -// Negated returns a negated vector -func (v Vector) Negated() Vector { - return Vector{-v.X, -v.Y} -} - -// Length returns the length of the vector -func (v Vector) Length() float64 { - return math.Sqrt(float64(v.X*v.X + v.Y*v.Y)) -} - -// Distance returns the distance between two vectors -func (v Vector) Distance(v2 Vector) float64 { - // Negate the two vectors and calciate the length - return v.Added(v2.Negated()).Length() -} - -// Multiplied returns the vector multiplied by an int -func (v Vector) Multiplied(val int) Vector { - return Vector{v.X * val, v.Y * val} -} - -// Divided returns the vector divided by an int -func (v Vector) Divided(val int) Vector { - return Vector{v.X / val, v.Y / val} -} - -// Direction describes a compass direction -type Direction int - -const ( - North Direction = iota - NorthEast - East - SouthEast - South - SouthWest - West - NorthWest -) - -// DirectionString simply describes the strings associated with a direction -type DirectionString struct { - Long string - Short string -} - -// DirectionStrings is the set of strings for each direction -var DirectionStrings = []DirectionString{ - {"North", "N"}, - {"NorthEast", "NE"}, - {"East", "E"}, - {"SouthEast", "SE"}, - {"South", "S"}, - {"SouthWest", "SW"}, - {"West", "W"}, - {"NorthWest", "NW"}, -} - -// String converts a Direction to a String -func (d Direction) String() string { - return DirectionStrings[d].Long -} - -// ShortString converts a Direction to a short string version -func (d Direction) ShortString() string { - return DirectionStrings[d].Short -} - -// DirectionFromString gets the Direction from a string -func DirectionFromString(s string) (Direction, error) { - for i, d := range DirectionStrings { - if strings.ToLower(d.Long) == strings.ToLower(s) || strings.ToLower(d.Short) == strings.ToLower(s) { - return Direction(i), nil - } - } - return -1, fmt.Errorf("Unknown direction: %s", s) -} - -var DirectionVectors = []Vector{ - {0, 1}, // N - {1, 1}, // NE - {1, 0}, // E - {1, -1}, // SE - {0, -1}, // S - {-1, 1}, // SW - {-1, 0}, // W - {-1, 1}, // NW -} - -// Vector converts a Direction to a Vector -func (d Direction) Vector() Vector { - return DirectionVectors[d] -} diff --git a/pkg/game/rover.go b/pkg/game/rover.go index 3952774..53ba7ff 100644 --- a/pkg/game/rover.go +++ b/pkg/game/rover.go @@ -1,6 +1,9 @@ package game -import "github.com/google/uuid" +import ( + "github.com/google/uuid" + "github.com/mdiluz/rove/pkg/vector" +) // RoverAttributes contains attributes of a rover type RoverAttributes struct { @@ -14,7 +17,7 @@ type RoverAttributes struct { Name string `json:"name"` // Pos represents where this rover is in the world - Pos Vector `json:"pos"` + Pos vector.Vector `json:"pos"` } // Rover describes a single rover in the world diff --git a/pkg/game/world.go b/pkg/game/world.go index 2d02c52..466c895 100644 --- a/pkg/game/world.go +++ b/pkg/game/world.go @@ -8,6 +8,9 @@ import ( "sync" "github.com/google/uuid" + "github.com/mdiluz/rove/pkg/bearing" + "github.com/mdiluz/rove/pkg/maths" + "github.com/mdiluz/rove/pkg/vector" "github.com/tjarratt/babble" ) @@ -70,9 +73,9 @@ func (w *World) SpawnRover() (uuid.UUID, error) { strings.ReplaceAll(rover.Attributes.Name, "'s", "") // Spawn in a random place near the origin - rover.Attributes.Pos = Vector{ - w.Atlas.ChunkSize/2 - rand.Intn(w.Atlas.ChunkSize), - w.Atlas.ChunkSize/2 - rand.Intn(w.Atlas.ChunkSize), + rover.Attributes.Pos = vector.Vector{ + X: w.Atlas.ChunkSize/2 - rand.Intn(w.Atlas.ChunkSize), + Y: w.Atlas.ChunkSize/2 - rand.Intn(w.Atlas.ChunkSize), } // Seach until we error (run out of world) @@ -84,7 +87,7 @@ func (w *World) SpawnRover() (uuid.UUID, error) { break } else { // Try and spawn to the east of the blockage - rover.Attributes.Pos.Add(Vector{1, 0}) + rover.Attributes.Pos.Add(vector.Vector{X: 1, Y: 0}) } } } @@ -146,7 +149,7 @@ func (w *World) SetRoverAttributes(id uuid.UUID, attributes RoverAttributes) err } // WarpRover sets an rovers position -func (w *World) WarpRover(id uuid.UUID, pos Vector) error { +func (w *World) WarpRover(id uuid.UUID, pos vector.Vector) error { w.worldMutex.Lock() defer w.worldMutex.Unlock() @@ -176,7 +179,7 @@ func (w *World) WarpRover(id uuid.UUID, pos Vector) error { } // SetPosition sets an rovers position -func (w *World) MoveRover(id uuid.UUID, bearing Direction) (RoverAttributes, error) { +func (w *World) MoveRover(id uuid.UUID, bearing bearing.Direction) (RoverAttributes, error) { w.worldMutex.Lock() defer w.worldMutex.Unlock() @@ -224,31 +227,31 @@ func (w *World) RadarFromRover(id uuid.UUID) ([]Tile, error) { roverPos := r.Attributes.Pos // Get the radar min and max values - radarMin := Vector{ + radarMin := vector.Vector{ X: roverPos.X - r.Attributes.Range, Y: roverPos.Y - r.Attributes.Range, } - radarMax := Vector{ + radarMax := vector.Vector{ X: roverPos.X + r.Attributes.Range, Y: roverPos.Y + r.Attributes.Range, } // Make sure we only query within the actual world worldMin, worldMax := w.Atlas.GetWorldExtents() - scanMin := Vector{ - X: Max(radarMin.X, worldMin.X), - Y: Max(radarMin.Y, worldMin.Y), + scanMin := vector.Vector{ + X: maths.Max(radarMin.X, worldMin.X), + Y: maths.Max(radarMin.Y, worldMin.Y), } - scanMax := Vector{ - X: Min(radarMax.X, worldMax.X), - Y: Min(radarMax.Y, worldMax.Y), + scanMax := vector.Vector{ + X: maths.Min(radarMax.X, worldMax.X), + Y: maths.Min(radarMax.Y, worldMax.Y), } // Gather up all tiles within the range var radar = make([]Tile, radarSpan*radarSpan) for j := scanMin.Y; j <= scanMax.Y; j++ { for i := scanMin.X; i <= scanMax.X; i++ { - q := Vector{i, j} + q := vector.Vector{X: i, Y: j} if tile, err := w.Atlas.GetTile(q); err != nil { return nil, fmt.Errorf("failed to query tile: %s", err) @@ -275,7 +278,7 @@ func (w *World) Enqueue(rover uuid.UUID, commands ...Command) error { for _, c := range commands { switch c.Command { case "move": - if _, err := DirectionFromString(c.Bearing); err != nil { + if _, err := bearing.DirectionFromString(c.Bearing); err != nil { return fmt.Errorf("unknown direction: %s", c.Bearing) } default: @@ -330,7 +333,7 @@ func (w *World) ExecuteCommand(c *Command, rover uuid.UUID) (finished bool, err switch c.Command { case "move": - if dir, err := DirectionFromString(c.Bearing); err != nil { + if dir, err := bearing.DirectionFromString(c.Bearing); err != nil { return true, fmt.Errorf("unknown direction in command %+v, skipping: %s\n", c, err) } else if _, err := w.MoveRover(rover, dir); err != nil { diff --git a/pkg/game/world_test.go b/pkg/game/world_test.go index d478b2c..1b6f1b6 100644 --- a/pkg/game/world_test.go +++ b/pkg/game/world_test.go @@ -3,6 +3,8 @@ package game import ( "testing" + "github.com/mdiluz/rove/pkg/bearing" + "github.com/mdiluz/rove/pkg/vector" "github.com/stretchr/testify/assert" ) @@ -65,7 +67,7 @@ func TestWorld_GetSetMovePosition(t *testing.T) { attribs, err := world.RoverAttributes(a) assert.NoError(t, err, "Failed to get rover attribs") - pos := Vector{ + pos := vector.Vector{ X: 0.0, Y: 0.0, } @@ -77,15 +79,15 @@ func TestWorld_GetSetMovePosition(t *testing.T) { assert.NoError(t, err, "Failed to set position for rover") assert.Equal(t, pos, newAttribs.Pos, "Failed to correctly set position for rover") - bearing := North + bearing := bearing.North duration := 1 newAttribs, err = world.MoveRover(a, bearing) assert.NoError(t, err, "Failed to set position for rover") - pos.Add(Vector{0, attribs.Speed * duration}) // We should have move one unit of the speed north + pos.Add(vector.Vector{X: 0, Y: attribs.Speed * duration}) // We should have move one unit of the speed north assert.Equal(t, pos, newAttribs.Pos, "Failed to correctly move position for rover") // Place a tile in front of the rover - assert.NoError(t, world.Atlas.SetTile(Vector{0, 2}, TileWall)) + assert.NoError(t, world.Atlas.SetTile(vector.Vector{X: 0, Y: 2}, TileWall)) newAttribs, err = world.MoveRover(a, bearing) assert.Equal(t, pos, newAttribs.Pos, "Failed to correctly not move position for rover into wall") } @@ -106,9 +108,9 @@ func TestWorld_RadarFromRover(t *testing.T) { assert.NoError(t, err, "Failed to set rover attribs") // Warp the rovers into position - bpos := Vector{-3, -3} + bpos := vector.Vector{X: -3, Y: -3} assert.NoError(t, world.WarpRover(b, bpos), "Failed to warp rover") - assert.NoError(t, world.WarpRover(a, Vector{0, 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() diff --git a/pkg/maths/maths.go b/pkg/maths/maths.go new file mode 100644 index 0000000..a3411b9 --- /dev/null +++ b/pkg/maths/maths.go @@ -0,0 +1,41 @@ +package maths + +// Abs gets the absolute value of an int +func Abs(x int) int { + if x < 0 { + return -x + } + return x +} + +// pmod is a mositive modulo +// golang's % is a "remainder" function si misbehaves for negative modulus inputs +func Pmod(x, d int) int { + if x == 0 || d == 0 { + return 0 + } + x = x % d + if x >= 0 { + return x + } else if d < 0 { + return x - d + } else { + return x + d + } +} + +// 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 +} diff --git a/pkg/maths/maths_test.go b/pkg/maths/maths_test.go new file mode 100644 index 0000000..cf2c180 --- /dev/null +++ b/pkg/maths/maths_test.go @@ -0,0 +1,33 @@ +package maths + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAbs(t *testing.T) { + assert.Equal(t, 0, Abs(0)) + assert.Equal(t, 1, Abs(1)) + assert.Equal(t, 1, Abs(-1)) +} + +func TestPmod(t *testing.T) { + assert.Equal(t, 0, Pmod(0, 0)) + assert.Equal(t, 2, Pmod(6, 4)) + assert.Equal(t, 2, Pmod(-6, 4)) + assert.Equal(t, 4, Pmod(-6, 10)) +} + +func TestMax(t *testing.T) { + assert.Equal(t, 500, Max(100, 500)) + assert.Equal(t, 1, Max(-4, 1)) + assert.Equal(t, -2, Max(-4, -2)) +} + +func TestMin(t *testing.T) { + assert.Equal(t, 100, Min(100, 500)) + assert.Equal(t, -4, Min(-4, 1)) + assert.Equal(t, -4, Min(-4, -2)) + +} diff --git a/pkg/server/routes_test.go b/pkg/server/routes_test.go index ce5cffa..46776d2 100644 --- a/pkg/server/routes_test.go +++ b/pkg/server/routes_test.go @@ -10,6 +10,7 @@ import ( "github.com/mdiluz/rove/pkg/game" "github.com/mdiluz/rove/pkg/rove" + "github.com/mdiluz/rove/pkg/vector" "github.com/stretchr/testify/assert" ) @@ -91,7 +92,7 @@ func TestHandleCommand(t *testing.T) { // Spawn the rover rover for the account _, inst, err := s.SpawnRoverForAccount(a.Id) - assert.NoError(t, s.world.WarpRover(inst, game.Vector{})) + assert.NoError(t, s.world.WarpRover(inst, vector.Vector{})) attribs, err := s.world.RoverAttributes(inst) assert.NoError(t, err, "Couldn't get rover position") @@ -130,7 +131,7 @@ func TestHandleCommand(t *testing.T) { attribs2, err := s.world.RoverAttributes(inst) assert.NoError(t, err, "Couldn't get rover position") - attribs.Pos.Add(game.Vector{X: 0.0, Y: attrib.Speed * 1}) // Should have moved north by the speed and duration + attribs.Pos.Add(vector.Vector{X: 0.0, Y: attrib.Speed * 1}) // Should have moved north by the speed and duration assert.Equal(t, attribs.Pos, attribs2.Pos, "Rover should have moved by bearing") } @@ -145,13 +146,13 @@ func TestHandleRadar(t *testing.T) { assert.NoError(t, err) // Warp this rover to 0,0 - assert.NoError(t, s.world.WarpRover(id, game.Vector{})) + assert.NoError(t, s.world.WarpRover(id, vector.Vector{})) // 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} + wallPos1 := vector.Vector{X: 0, Y: -1} + wallPos2 := vector.Vector{X: 1, Y: 1} + rockPos := vector.Vector{X: 1, Y: 3} + emptyPos := vector.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)) @@ -171,7 +172,7 @@ func TestHandleRadar(t *testing.T) { } scope := attrib.Range*2 + 1 - radarOrigin := game.Vector{X: -attrib.Range, Y: -attrib.Range} + radarOrigin := vector.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]) diff --git a/pkg/vector/vector.go b/pkg/vector/vector.go new file mode 100644 index 0000000..f6c8215 --- /dev/null +++ b/pkg/vector/vector.go @@ -0,0 +1,47 @@ +package vector + +import "math" + +// Vector desribes a 3D vector +type Vector struct { + X int `json:"x"` + Y int `json:"y"` +} + +// Add adds one vector to another +func (v *Vector) Add(v2 Vector) { + v.X += v2.X + v.Y += v2.Y +} + +// Added calculates a new vector +func (v Vector) Added(v2 Vector) Vector { + v.Add(v2) + return v +} + +// Negated returns a negated vector +func (v Vector) Negated() Vector { + return Vector{-v.X, -v.Y} +} + +// Length returns the length of the vector +func (v Vector) Length() float64 { + return math.Sqrt(float64(v.X*v.X + v.Y*v.Y)) +} + +// Distance returns the distance between two vectors +func (v Vector) Distance(v2 Vector) float64 { + // Negate the two vectors and calciate the length + return v.Added(v2.Negated()).Length() +} + +// Multiplied returns the vector multiplied by an int +func (v Vector) Multiplied(val int) Vector { + return Vector{v.X * val, v.Y * val} +} + +// Divided returns the vector divided by an int +func (v Vector) Divided(val int) Vector { + return Vector{v.X / val, v.Y / val} +} diff --git a/pkg/game/math_test.go b/pkg/vector/vector_test.go similarity index 83% rename from pkg/game/math_test.go rename to pkg/vector/vector_test.go index 440dd23..5d92136 100644 --- a/pkg/game/math_test.go +++ b/pkg/vector/vector_test.go @@ -1,4 +1,4 @@ -package game +package vector import ( "math" @@ -180,15 +180,15 @@ func TestVector_Multiplied(t *testing.T) { }{ { name: "Basic multiply 1", - vec: North.Vector(), + vec: Vector{0, 1}, arg: 2, want: Vector{0, 2}, }, { name: "Basic multiply 2", - vec: NorthWest.Vector(), + vec: Vector{-1, 1}, arg: -1, - want: SouthEast.Vector(), + want: Vector{1, -1}, }, } for _, tt := range tests { @@ -203,27 +203,3 @@ func TestVector_Multiplied(t *testing.T) { }) } } - -func TestDirection(t *testing.T) { - dir := North - - assert.Equal(t, "North", dir.String()) - assert.Equal(t, "N", dir.ShortString()) - assert.Equal(t, Vector{0, 1}, dir.Vector()) - - dir, err := DirectionFromString("N") - assert.NoError(t, err) - assert.Equal(t, North, dir) - - dir, err = DirectionFromString("n") - assert.NoError(t, err) - assert.Equal(t, North, dir) - - dir, err = DirectionFromString("north") - assert.NoError(t, err) - assert.Equal(t, North, dir) - - dir, err = DirectionFromString("NorthWest") - assert.NoError(t, err) - assert.Equal(t, NorthWest, dir) -}