Convert bearings to compass points and locations to int coords
This commit is contained in:
parent
ae369715ec
commit
be0f4f1aff
10 changed files with 187 additions and 35 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================
|
// ================
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue