Convert bearings to compass points and locations to int coords

This commit is contained in:
Marc Di Luzio 2020-06-05 16:37:52 +01:00
parent ae369715ec
commit be0f4f1aff
10 changed files with 187 additions and 35 deletions

View file

@ -6,8 +6,8 @@ For a proof of concept we'll implement a very small subset of the final features
### Core Features ### Core Features
* Create an account (done) * Create an account (done)
* Control an entity in a 2D map (done) * Control an rover in a 2D map (done)
* Query for what the entity can see * Query for what the rover can see (done)
* Persistent accounts and world (done) * Persistent accounts and world (done)
* Multiple users (done) * Multiple users (done)
* Populate map with locations/objects * Populate map with locations/objects

View file

@ -6,7 +6,7 @@ import "github.com/google/uuid"
type Command func() error type Command func() error
// CommandMove will move the rover in question // CommandMove will move the rover in question
func (w *World) CommandMove(id uuid.UUID, bearing float64, duration float64) Command { func (w *World) CommandMove(id uuid.UUID, bearing Direction, duration int) Command {
return func() error { return func() error {
_, err := w.MoveRover(id, bearing, duration) _, err := w.MoveRover(id, bearing, duration)
return err return err

View file

@ -20,14 +20,14 @@ func TestCommand_Move(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")
bearing := 0.0 bearing := North
duration := 1.0 duration := 1
// Try the move command // Try the move command
moveCommand := world.CommandMove(a, bearing, duration) moveCommand := world.CommandMove(a, bearing, duration)
assert.NoError(t, world.Execute(moveCommand), "Failed to execute move command") assert.NoError(t, world.Execute(moveCommand), "Failed to execute move command")
newpos, err := world.RoverPosition(a) newpos, err := world.RoverPosition(a)
assert.NoError(t, err, "Failed to set position for rover") assert.NoError(t, err, "Failed to set position for rover")
pos.Add(Vector{0.0, float64(duration) * attribs.Speed}) // We should have moved duration*speed north pos.Add(Vector{0.0, duration * attribs.Speed}) // We should have moved duration*speed north
assert.Equal(t, pos, newpos, "Failed to correctly set position for rover") assert.Equal(t, pos, newpos, "Failed to correctly set position for rover")
} }

View file

@ -1,11 +1,15 @@
package game package game
import "math" import (
"fmt"
"math"
"strings"
)
// Vector desribes a 3D vector // Vector desribes a 3D vector
type Vector struct { type Vector struct {
X float64 `json:"x"` X int `json:"x"`
Y float64 `json:"y"` Y int `json:"y"`
} }
// Add adds one vector to another // Add adds one vector to another
@ -27,7 +31,7 @@ func (v Vector) Negated() Vector {
// Length returns the length of the vector // Length returns the length of the vector
func (v Vector) Length() float64 { func (v Vector) Length() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y) return math.Sqrt(float64(v.X*v.X + v.Y*v.Y))
} }
// Distance returns the distance between two vectors // Distance returns the distance between two vectors
@ -35,3 +39,76 @@ func (v Vector) Distance(v2 Vector) float64 {
// Negate the two vectors and calciate the length // Negate the two vectors and calciate the length
return v.Added(v2.Negated()).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}
}
// 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]
}

View file

@ -169,3 +169,61 @@ func TestVector_Distance(t *testing.T) {
}) })
} }
} }
func TestVector_Multiplied(t *testing.T) {
tests := []struct {
name string
vec Vector
arg int
want Vector
}{
{
name: "Basic multiply 1",
vec: North.Vector(),
arg: 2,
want: Vector{0, 2},
},
{
name: "Basic multiply 2",
vec: NorthWest.Vector(),
arg: -1,
want: SouthEast.Vector(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := Vector{
X: tt.vec.X,
Y: tt.vec.Y,
}
if got := v.Multiplied(tt.arg); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Vector.Multiplied() = %v, want %v", got, tt.want)
}
})
}
}
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)
}

View file

@ -2,7 +2,6 @@ package game
import ( import (
"fmt" "fmt"
"math"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -16,10 +15,10 @@ type World struct {
// RoverAttributes contains attributes of a rover // RoverAttributes contains attributes of a rover
type RoverAttributes struct { type RoverAttributes struct {
// Speed represents the Speed that the rover will move per second // Speed represents the Speed that the rover will move per second
Speed float64 `json:"speed"` Speed int `json:"speed"`
// Range represents the distance the unit's radar can see // Range represents the distance the unit's radar can see
Range float64 `json:"range"` Range int `json:"range"`
} }
// Rover describes a single rover in the world // Rover describes a single rover in the world
@ -103,16 +102,13 @@ func (w *World) WarpRover(id uuid.UUID, pos Vector) error {
} }
// SetPosition sets an rovers position // SetPosition sets an rovers position
func (w *World) MoveRover(id uuid.UUID, bearing float64, duration float64) (Vector, error) { func (w *World) MoveRover(id uuid.UUID, bearing Direction, duration int) (Vector, error) {
if i, ok := w.Rovers[id]; ok { if i, ok := w.Rovers[id]; ok {
// Calculate the distance // Calculate the distance
distance := i.Attributes.Speed * float64(duration) distance := i.Attributes.Speed * duration
// Calculate the full movement based on the bearing // Calculate the full movement based on the bearing
move := Vector{ move := bearing.Vector().Multiplied(distance)
X: math.Sin(bearing) * distance,
Y: math.Cos(bearing) * distance,
}
// Increment the position by the movement // Increment the position by the movement
i.Pos.Add(move) i.Pos.Add(move)
@ -138,7 +134,7 @@ func (w World) RadarFromRover(id uuid.UUID) (RadarDescription, error) {
// Gather nearby rovers within the range // Gather nearby rovers within the range
for _, r2 := range w.Rovers { for _, r2 := range w.Rovers {
if r1.Id != r2.Id && r1.Pos.Distance(r2.Pos) < r1.Attributes.Range { if r1.Id != r2.Id && r1.Pos.Distance(r2.Pos) < float64(r1.Attributes.Range) {
desc.Rovers = append(desc.Rovers, r2.Pos) desc.Rovers = append(desc.Rovers, r2.Pos)
} }
} }

View file

@ -71,11 +71,11 @@ func TestWorld_GetSetMovePosition(t *testing.T) {
assert.NoError(t, err, "Failed to set position for rover") assert.NoError(t, err, "Failed to set position for rover")
assert.Equal(t, pos, newpos, "Failed to correctly set position for rover") assert.Equal(t, pos, newpos, "Failed to correctly set position for rover")
bearing := 0.0 bearing := North
duration := 1.0 duration := 1
newpos, err = world.MoveRover(a, bearing, duration) newpos, err = world.MoveRover(a, bearing, duration)
assert.NoError(t, err, "Failed to set position for rover") assert.NoError(t, err, "Failed to set position for rover")
pos.Add(Vector{0, attribs.Speed * float64(duration)}) // We should have move one unit of the speed north pos.Add(Vector{0, attribs.Speed * duration}) // We should have move one unit of the speed north
assert.Equal(t, pos, newpos, "Failed to correctly move position for rover") assert.Equal(t, pos, newpos, "Failed to correctly move position for rover")
} }

View file

@ -70,6 +70,17 @@ const (
CommandMove = "move" CommandMove = "move"
) )
const (
BearingNorth = "North"
BearingNorthEast
BearingEast
BearingSouthEast
BearingSouth
BearingSouthWest
BearingWest
BearingNorthWest
)
// Command describes a single command to execute // Command describes a single command to execute
// it contains the type, and then any members used for each command type // it contains the type, and then any members used for each command type
type Command struct { type Command struct {
@ -77,8 +88,8 @@ type Command struct {
Command string `json:"command"` Command string `json:"command"`
// Used for CommandMove // Used for CommandMove
Bearing float64 `json:"bearing"` // The direction to move in degrees Bearing string `json:"bearing"` // The direction to move on a compass in short (NW) or long (NorthWest) form
Duration float64 `json:"duration"` // The duration of the move in seconds Duration int `json:"duration"` // The duration of the move in ticks
} }
// ================ // ================

View file

@ -148,6 +148,22 @@ func HandleSpawn(s *Server, b io.ReadCloser, w io.Writer) error {
return nil return nil
} }
// ConvertCommands converts server commands to game commands
func (s *Server) ConvertCommands(commands []Command, inst uuid.UUID) ([]game.Command, error) {
var cmds []game.Command
for _, c := range commands {
switch c.Command {
case CommandMove:
if bearing, err := game.DirectionFromString(c.Bearing); err != nil {
return nil, err
} else {
cmds = append(cmds, s.world.CommandMove(inst, bearing, c.Duration))
}
}
}
return cmds, nil
}
// HandleSpawn will spawn the player entity for the associated account // HandleSpawn will spawn the player entity for the associated account
func HandleCommands(s *Server, b io.ReadCloser, w io.Writer) error { func HandleCommands(s *Server, b io.ReadCloser, w io.Writer) error {
// Set up the response // Set up the response
@ -170,19 +186,13 @@ func HandleCommands(s *Server, b io.ReadCloser, w io.Writer) error {
} else if inst, err := s.accountant.GetRover(id); err != nil { } else if inst, err := s.accountant.GetRover(id); err != nil {
response.Error = fmt.Sprintf("Provided account has no rover: %s", err) response.Error = fmt.Sprintf("Provided account has no rover: %s", err)
} else if cmds, err := s.ConvertCommands(data.Commands, inst); err != nil {
response.Error = fmt.Sprintf("Couldn't convert commands: %s", err)
} else { } else {
// log the data sent // log the data sent
fmt.Printf("\tcommands data: %v\n", data) fmt.Printf("\tcommands data: %v\n", data)
// Iterate through the commands to generate all game commands
var cmds []game.Command
for _, c := range data.Commands {
switch c.Command {
case CommandMove:
cmds = append(cmds, s.world.CommandMove(inst, c.Bearing, c.Duration))
}
}
// Execute the commands // Execute the commands
if err := s.world.Execute(cmds...); err != nil { if err := s.world.Execute(cmds...); err != nil {
response.Error = fmt.Sprintf("Failed to execute commands: %s", err) response.Error = fmt.Sprintf("Failed to execute commands: %s", err)

View file

@ -89,7 +89,7 @@ func TestHandleCommands(t *testing.T) {
Commands: []Command{ Commands: []Command{
{ {
Command: CommandMove, Command: CommandMove,
Bearing: 0.0, Bearing: "N",
Duration: 1, Duration: 1,
}, },
}, },