From 4e0e55af8822b12a27ca05292fa3d0f5b8c4a560 Mon Sep 17 00:00:00 2001
From: Marc Di Luzio <marc.diluzio@gmail.com>
Date: Sun, 19 Jul 2020 12:54:41 +0100
Subject: [PATCH 1/2] Move bearing into proto file

---
 cmd/rove/main.go            |  24 ++-
 pkg/maths/bearing.go        |  97 ----------
 pkg/maths/bearing_test.go   |  31 ---
 pkg/maths/vector.go         |  18 ++
 pkg/rove/command.go         |   2 +-
 pkg/rove/command_test.go    |   4 +-
 pkg/rove/world.go           |  12 +-
 pkg/rove/world_test.go      |  14 +-
 proto/roveapi/roveapi.pb.go | 372 +++++++++++++++++++++---------------
 proto/roveapi/roveapi.proto |  13 +-
 10 files changed, 281 insertions(+), 306 deletions(-)
 delete mode 100644 pkg/maths/bearing.go
 delete mode 100644 pkg/maths/bearing_test.go

diff --git a/cmd/rove/main.go b/cmd/rove/main.go
index 17d8dd4..dd8c39c 100644
--- a/cmd/rove/main.go
+++ b/cmd/rove/main.go
@@ -10,7 +10,6 @@ import (
 	"path/filepath"
 	"time"
 
-	"github.com/mdiluz/rove/pkg/maths"
 	"github.com/mdiluz/rove/pkg/version"
 	"github.com/mdiluz/rove/proto/roveapi"
 	"golang.org/x/net/context"
@@ -123,6 +122,21 @@ func checkAccount(a Account) error {
 	return nil
 }
 
+// StringToBearing converts a string to a bearing
+func StringToBearing(s string) roveapi.Bearing {
+	switch s {
+	case "N":
+		return roveapi.Bearing_North
+	case "E":
+		return roveapi.Bearing_East
+	case "S":
+		return roveapi.Bearing_South
+	case "W":
+		return roveapi.Bearing_West
+	}
+	return roveapi.Bearing_BearingUnknown
+}
+
 // InnerMain wraps the main function so we can test it
 func InnerMain(command string, args ...string) error {
 
@@ -214,13 +228,15 @@ func InnerMain(command string, args ...string) error {
 				i++
 				if len(args) == i {
 					return fmt.Errorf("move command must be passed bearing")
-				} else if _, err := maths.BearingFromString(args[i]); err != nil {
-					return err
+				}
+				var b roveapi.Bearing
+				if b = StringToBearing(args[i]); b == roveapi.Bearing_BearingUnknown {
+					return fmt.Errorf("unrecognised bearing: %s", args[i])
 				}
 				commands = append(commands,
 					&roveapi.Command{
 						Command: roveapi.CommandType_move,
-						Data:    &roveapi.Command_Bearing{Bearing: args[i]},
+						Data:    &roveapi.Command_Bearing{Bearing: b},
 					},
 				)
 			case "broadcast":
diff --git a/pkg/maths/bearing.go b/pkg/maths/bearing.go
deleted file mode 100644
index e72148b..0000000
--- a/pkg/maths/bearing.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package maths
-
-import (
-	"fmt"
-	"strings"
-)
-
-// Bearing describes a compass direction
-type Bearing int
-
-const (
-	// North describes a 0,1 vector
-	North Bearing = iota
-	// NorthEast describes a 1,1 vector
-	NorthEast
-	// East describes a 1,0 vector
-	East
-	// SouthEast describes a 1,-1 vector
-	SouthEast
-	// South describes a 0,-1 vector
-	South
-	// SouthWest describes a -1,-1 vector
-	SouthWest
-	// West describes a -1,0 vector
-	West
-	// NorthWest describes a -1,1 vector
-	NorthWest
-)
-
-// bearingString simply describes the strings associated with a direction
-type bearingString struct {
-	Long  string
-	Short string
-}
-
-// bearingStrings is the set of strings for each direction
-var bearingStrings = []bearingString{
-	{"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 Bearing) String() string {
-	return bearingStrings[d].Long
-}
-
-// ShortString converts a Direction to a short string version
-func (d Bearing) ShortString() string {
-	return bearingStrings[d].Short
-}
-
-// BearingFromString gets the Direction from a string
-func BearingFromString(s string) (Bearing, error) {
-	for i, d := range bearingStrings {
-		if strings.EqualFold(d.Long, s) || strings.EqualFold(d.Short, s) {
-			return Bearing(i), nil
-		}
-	}
-	return -1, fmt.Errorf("unknown bearing: %s", s)
-}
-
-var bearingVectors = []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 Bearing) Vector() Vector {
-	return bearingVectors[d]
-}
-
-// IsCardinal returns if this is a cardinal (NESW)
-func (d Bearing) IsCardinal() bool {
-	switch d {
-	case North:
-		fallthrough
-	case East:
-		fallthrough
-	case South:
-		fallthrough
-	case West:
-		return true
-	}
-	return false
-}
diff --git a/pkg/maths/bearing_test.go b/pkg/maths/bearing_test.go
deleted file mode 100644
index e0de345..0000000
--- a/pkg/maths/bearing_test.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package maths
-
-import (
-	"testing"
-
-	"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{X: 0, Y: 1}, dir.Vector())
-
-	dir, err := BearingFromString("N")
-	assert.NoError(t, err)
-	assert.Equal(t, North, dir)
-
-	dir, err = BearingFromString("n")
-	assert.NoError(t, err)
-	assert.Equal(t, North, dir)
-
-	dir, err = BearingFromString("north")
-	assert.NoError(t, err)
-	assert.Equal(t, North, dir)
-
-	dir, err = BearingFromString("NorthWest")
-	assert.NoError(t, err)
-	assert.Equal(t, NorthWest, dir)
-}
diff --git a/pkg/maths/vector.go b/pkg/maths/vector.go
index 2dd3bad..8a63708 100644
--- a/pkg/maths/vector.go
+++ b/pkg/maths/vector.go
@@ -2,6 +2,8 @@ package maths
 
 import (
 	"math"
+
+	"github.com/mdiluz/rove/proto/roveapi"
 )
 
 // Vector desribes a 3D vector
@@ -81,3 +83,19 @@ func Min2(v1 Vector, v2 Vector) Vector {
 func Max2(v1 Vector, v2 Vector) Vector {
 	return Vector{Max(v1.X, v2.X), Max(v1.Y, v2.Y)}
 }
+
+// BearingToVector converts a bearing to a vector
+func BearingToVector(b roveapi.Bearing) Vector {
+	switch b {
+	case roveapi.Bearing_North:
+		return Vector{Y: 1}
+	case roveapi.Bearing_East:
+		return Vector{X: 1}
+	case roveapi.Bearing_South:
+		return Vector{Y: -1}
+	case roveapi.Bearing_West:
+		return Vector{X: -1}
+	}
+
+	return Vector{}
+}
diff --git a/pkg/rove/command.go b/pkg/rove/command.go
index 86e9cad..e64d646 100644
--- a/pkg/rove/command.go
+++ b/pkg/rove/command.go
@@ -7,7 +7,7 @@ type Command struct {
 	Command roveapi.CommandType `json:"command"`
 
 	// Used in the move command
-	Bearing string `json:"bearing,omitempty"`
+	Bearing roveapi.Bearing `json:"bearing,omitempty"`
 
 	// Used in the broadcast command
 	Message []byte `json:"message,omitempty"`
diff --git a/pkg/rove/command_test.go b/pkg/rove/command_test.go
index 9150fde..337782f 100644
--- a/pkg/rove/command_test.go
+++ b/pkg/rove/command_test.go
@@ -21,7 +21,7 @@ func TestCommand_Move(t *testing.T) {
 	assert.NoError(t, err, "Failed to set position for rover")
 
 	// Try the move command
-	moveCommand := Command{Command: roveapi.CommandType_move, Bearing: "N"}
+	moveCommand := Command{Command: roveapi.CommandType_move, Bearing: roveapi.Bearing_North}
 	assert.NoError(t, world.Enqueue(a, moveCommand), "Failed to execute move command")
 
 	// Tick the world
@@ -47,7 +47,7 @@ func TestCommand_Recharge(t *testing.T) {
 	assert.NoError(t, err, "Failed to set position for rover")
 
 	// Move to use up some charge
-	moveCommand := Command{Command: roveapi.CommandType_move, Bearing: "N"}
+	moveCommand := Command{Command: roveapi.CommandType_move, Bearing: roveapi.Bearing_North}
 	assert.NoError(t, world.Enqueue(a, moveCommand), "Failed to queue move command")
 
 	// Tick the world
diff --git a/pkg/rove/world.go b/pkg/rove/world.go
index 4693555..a5ab05f 100644
--- a/pkg/rove/world.go
+++ b/pkg/rove/world.go
@@ -277,7 +277,7 @@ func (w *World) WarpRover(rover string, pos maths.Vector) error {
 }
 
 // MoveRover attempts to move a rover in a specific direction
-func (w *World) MoveRover(rover string, b maths.Bearing) (maths.Vector, error) {
+func (w *World) MoveRover(rover string, b roveapi.Bearing) (maths.Vector, error) {
 	w.worldMutex.Lock()
 	defer w.worldMutex.Unlock()
 
@@ -293,7 +293,7 @@ func (w *World) MoveRover(rover string, b maths.Bearing) (maths.Vector, error) {
 	i.Charge--
 
 	// Try the new move position
-	newPos := i.Pos.Added(b.Vector())
+	newPos := i.Pos.Added(maths.BearingToVector(b))
 
 	// Get the tile and verify it's empty
 	_, obj := w.Atlas.QueryPosition(newPos)
@@ -426,9 +426,7 @@ func (w *World) Enqueue(rover string, commands ...Command) error {
 	for _, c := range commands {
 		switch c.Command {
 		case roveapi.CommandType_move:
-			if b, err := maths.BearingFromString(c.Bearing); err != nil {
-				return fmt.Errorf("unknown bearing: %s", c.Bearing)
-			} else if !b.IsCardinal() {
+			if c.Bearing == roveapi.Bearing_BearingUnknown {
 				return fmt.Errorf("bearing must be cardinal")
 			}
 		case roveapi.CommandType_broadcast:
@@ -507,9 +505,7 @@ func (w *World) ExecuteCommand(c *Command, rover string) (err error) {
 
 	switch c.Command {
 	case roveapi.CommandType_move:
-		if dir, err := maths.BearingFromString(c.Bearing); err != nil {
-			return err
-		} else if _, err := w.MoveRover(rover, dir); err != nil {
+		if _, err := w.MoveRover(rover, c.Bearing); err != nil {
 			return err
 		}
 
diff --git a/pkg/rove/world_test.go b/pkg/rove/world_test.go
index 6534d89..7cf2483 100644
--- a/pkg/rove/world_test.go
+++ b/pkg/rove/world_test.go
@@ -78,7 +78,7 @@ func TestWorld_GetSetMovePosition(t *testing.T) {
 	assert.NoError(t, err, "Failed to set position for rover")
 	assert.Equal(t, pos, newPos, "Failed to correctly set position for rover")
 
-	b := maths.North
+	b := roveapi.Bearing_North
 	newPos, err = world.MoveRover(a, b)
 	assert.NoError(t, err, "Failed to set position for rover")
 	pos.Add(maths.Vector{X: 0, Y: 1})
@@ -223,7 +223,7 @@ func TestWorld_RoverDamage(t *testing.T) {
 
 	world.Atlas.SetObject(maths.Vector{X: 0.0, Y: 1.0}, atlas.Object{Type: roveapi.Object_RockLarge})
 
-	vec, err := world.MoveRover(a, maths.North)
+	vec, err := world.MoveRover(a, roveapi.Bearing_North)
 	assert.NoError(t, err, "Failed to move rover")
 	assert.Equal(t, pos, vec, "Rover managed to move into large rock")
 
@@ -260,7 +260,7 @@ func TestWorld_RoverRepair(t *testing.T) {
 	world.Atlas.SetObject(maths.Vector{X: 0.0, Y: 1.0}, atlas.Object{Type: roveapi.Object_RockLarge})
 
 	// Try and bump into the rock
-	vec, err := world.MoveRover(a, maths.North)
+	vec, err := world.MoveRover(a, roveapi.Bearing_North)
 	assert.NoError(t, err, "Failed to move rover")
 	assert.Equal(t, pos, vec, "Rover managed to move into large rock")
 
@@ -307,13 +307,13 @@ func TestWorld_Charge(t *testing.T) {
 		assert.NoError(t, err, "Failed to get position for rover")
 
 		// Ensure the path ahead is empty
-		world.Atlas.SetTile(initialPos.Added(maths.North.Vector()), roveapi.Tile_Rock)
-		world.Atlas.SetObject(initialPos.Added(maths.North.Vector()), atlas.Object{Type: roveapi.Object_ObjectUnknown})
+		world.Atlas.SetTile(initialPos.Added(maths.BearingToVector(roveapi.Bearing_North)), roveapi.Tile_Rock)
+		world.Atlas.SetObject(initialPos.Added(maths.BearingToVector(roveapi.Bearing_North)), atlas.Object{Type: roveapi.Object_ObjectUnknown})
 
 		// Try and move north (along unblocked path)
-		newPos, err := world.MoveRover(a, maths.North)
+		newPos, err := world.MoveRover(a, roveapi.Bearing_North)
 		assert.NoError(t, err, "Failed to set position for rover")
-		assert.Equal(t, initialPos.Added(maths.North.Vector()), newPos, "Failed to correctly move position for rover")
+		assert.Equal(t, initialPos.Added(maths.BearingToVector(roveapi.Bearing_North)), newPos, "Failed to correctly move position for rover")
 
 		// Ensure rover lost charge
 		rover, err := world.GetRover(a)
diff --git a/proto/roveapi/roveapi.pb.go b/proto/roveapi/roveapi.pb.go
index dd47ca9..e35c863 100644
--- a/proto/roveapi/roveapi.pb.go
+++ b/proto/roveapi/roveapi.pb.go
@@ -98,6 +98,62 @@ func (CommandType) EnumDescriptor() ([]byte, []int) {
 	return file_roveapi_roveapi_proto_rawDescGZIP(), []int{0}
 }
 
+type Bearing int32
+
+const (
+	// BearingUnknown an unknown invalid bearing
+	Bearing_BearingUnknown Bearing = 0
+	Bearing_North          Bearing = 1
+	Bearing_East           Bearing = 2
+	Bearing_South          Bearing = 3
+	Bearing_West           Bearing = 4
+)
+
+// Enum value maps for Bearing.
+var (
+	Bearing_name = map[int32]string{
+		0: "BearingUnknown",
+		1: "North",
+		2: "East",
+		3: "South",
+		4: "West",
+	}
+	Bearing_value = map[string]int32{
+		"BearingUnknown": 0,
+		"North":          1,
+		"East":           2,
+		"South":          3,
+		"West":           4,
+	}
+)
+
+func (x Bearing) Enum() *Bearing {
+	p := new(Bearing)
+	*p = x
+	return p
+}
+
+func (x Bearing) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Bearing) Descriptor() protoreflect.EnumDescriptor {
+	return file_roveapi_roveapi_proto_enumTypes[1].Descriptor()
+}
+
+func (Bearing) Type() protoreflect.EnumType {
+	return &file_roveapi_roveapi_proto_enumTypes[1]
+}
+
+func (x Bearing) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use Bearing.Descriptor instead.
+func (Bearing) EnumDescriptor() ([]byte, []int) {
+	return file_roveapi_roveapi_proto_rawDescGZIP(), []int{1}
+}
+
 // Types of objects
 type Object int32
 
@@ -139,11 +195,11 @@ func (x Object) String() string {
 }
 
 func (Object) Descriptor() protoreflect.EnumDescriptor {
-	return file_roveapi_roveapi_proto_enumTypes[1].Descriptor()
+	return file_roveapi_roveapi_proto_enumTypes[2].Descriptor()
 }
 
 func (Object) Type() protoreflect.EnumType {
-	return &file_roveapi_roveapi_proto_enumTypes[1]
+	return &file_roveapi_roveapi_proto_enumTypes[2]
 }
 
 func (x Object) Number() protoreflect.EnumNumber {
@@ -152,7 +208,7 @@ func (x Object) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use Object.Descriptor instead.
 func (Object) EnumDescriptor() ([]byte, []int) {
-	return file_roveapi_roveapi_proto_rawDescGZIP(), []int{1}
+	return file_roveapi_roveapi_proto_rawDescGZIP(), []int{2}
 }
 
 type Tile int32
@@ -195,11 +251,11 @@ func (x Tile) String() string {
 }
 
 func (Tile) Descriptor() protoreflect.EnumDescriptor {
-	return file_roveapi_roveapi_proto_enumTypes[2].Descriptor()
+	return file_roveapi_roveapi_proto_enumTypes[3].Descriptor()
 }
 
 func (Tile) Type() protoreflect.EnumType {
-	return &file_roveapi_roveapi_proto_enumTypes[2]
+	return &file_roveapi_roveapi_proto_enumTypes[3]
 }
 
 func (x Tile) Number() protoreflect.EnumNumber {
@@ -208,7 +264,7 @@ func (x Tile) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use Tile.Descriptor instead.
 func (Tile) EnumDescriptor() ([]byte, []int) {
-	return file_roveapi_roveapi_proto_rawDescGZIP(), []int{2}
+	return file_roveapi_roveapi_proto_rawDescGZIP(), []int{3}
 }
 
 // ServerStatusRequest is an empty placeholder
@@ -551,11 +607,11 @@ func (m *Command) GetData() isCommand_Data {
 	return nil
 }
 
-func (x *Command) GetBearing() string {
+func (x *Command) GetBearing() Bearing {
 	if x, ok := x.GetData().(*Command_Bearing); ok {
 		return x.Bearing
 	}
-	return ""
+	return Bearing_BearingUnknown
 }
 
 func (x *Command) GetMessage() []byte {
@@ -570,9 +626,9 @@ type isCommand_Data interface {
 }
 
 type Command_Bearing struct {
-	// A bearing, example: NE
+	// A bearing
 	// Used with MOVE
-	Bearing string `protobuf:"bytes,2,opt,name=bearing,proto3,oneof"`
+	Bearing Bearing `protobuf:"varint,2,opt,name=bearing,proto3,enum=roveapi.Bearing,oneof"`
 }
 
 type Command_Message struct {
@@ -1137,109 +1193,115 @@ var file_roveapi_roveapi_proto_rawDesc = []byte{
 	0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a,
 	0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10,
 	0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
-	0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x79, 0x0a, 0x07, 0x43, 0x6f, 0x6d,
-	0x6d, 0x61, 0x6e, 0x64, 0x12, 0x2e, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e,
-	0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x63, 0x6f, 0x6d,
-	0x6d, 0x61, 0x6e, 0x64, 0x12, 0x1a, 0x0a, 0x07, 0x62, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x62, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67,
-	0x12, 0x1a, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x0c, 0x48, 0x00, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x06, 0x0a, 0x04,
-	0x64, 0x61, 0x74, 0x61, 0x22, 0x6a, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52,
+	0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8b, 0x01, 0x0a, 0x07, 0x43, 0x6f,
+	0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x2e, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69,
+	0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x63, 0x6f,
+	0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x2c, 0x0a, 0x07, 0x62, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69,
+	0x2e, 0x42, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x48, 0x00, 0x52, 0x07, 0x62, 0x65, 0x61, 0x72,
+	0x69, 0x6e, 0x67, 0x12, 0x1a, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42,
+	0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x6a, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x61,
+	0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x61, 0x63, 0x63,
+	0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76,
+	0x65, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63,
+	0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
+	0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70,
+	0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
+	0x6e, 0x64, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x0a, 0x0c, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52,
 	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
 	0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70,
 	0x69, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75,
-	0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x18, 0x02,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43,
-	0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73,
-	0x22, 0x11, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x22, 0x3a, 0x0a, 0x0c, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x41,
-	0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22,
-	0x75, 0x0a, 0x0d, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x12, 0x14, 0x0a, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52,
-	0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x74, 0x69, 0x6c, 0x65, 0x73, 0x18,
-	0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e,
-	0x54, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x74, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x07, 0x6f,
-	0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x72,
-	0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x07, 0x6f,
-	0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x22, 0x3b, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75,
-	0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61,
-	0x70, 0x69, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f,
-	0x75, 0x6e, 0x74, 0x22, 0x2d, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69,
-	0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x12,
-	0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65,
-	0x78, 0x74, 0x22, 0x24, 0x0a, 0x06, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x0c, 0x0a, 0x01,
-	0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x78, 0x12, 0x0c, 0x0a, 0x01, 0x79, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x79, 0x22, 0xc3, 0x03, 0x0a, 0x0e, 0x53, 0x74, 0x61,
-	0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e,
-	0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
-	0x2b, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x0f, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x63, 0x74,
-	0x6f, 0x72, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05,
-	0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, 0x61, 0x6e,
-	0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79,
-	0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01,
-	0x28, 0x05, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x0a, 0x09,
-	0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52,
-	0x09, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2a, 0x0a, 0x10, 0x6d, 0x61,
-	0x78, 0x69, 0x6d, 0x75, 0x6d, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x18, 0x07,
-	0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x49, 0x6e, 0x74,
-	0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65,
-	0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, 0x24,
-	0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x18,
-	0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x68,
-	0x61, 0x72, 0x67, 0x65, 0x12, 0x3c, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67,
-	0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10,
-	0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
-	0x52, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
-	0x64, 0x73, 0x12, 0x38, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d,
-	0x61, 0x6e, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76,
-	0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x0e, 0x71, 0x75,
-	0x65, 0x75, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x12, 0x20, 0x0a, 0x04,
-	0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x72, 0x6f, 0x76,
-	0x65, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x2a, 0x55,
-	0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a,
-	0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x6d, 0x6f, 0x76, 0x65, 0x10,
-	0x01, 0x12, 0x09, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x73, 0x68, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06,
-	0x72, 0x65, 0x70, 0x61, 0x69, 0x72, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x68,
-	0x61, 0x72, 0x67, 0x65, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63,
-	0x61, 0x73, 0x74, 0x10, 0x05, 0x2a, 0x48, 0x0a, 0x06, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12,
-	0x11, 0x0a, 0x0d, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e,
-	0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x52, 0x6f, 0x76, 0x65, 0x72, 0x4c, 0x69, 0x76, 0x65, 0x10,
-	0x01, 0x12, 0x0d, 0x0a, 0x09, 0x52, 0x6f, 0x63, 0x6b, 0x53, 0x6d, 0x61, 0x6c, 0x6c, 0x10, 0x02,
-	0x12, 0x0d, 0x0a, 0x09, 0x52, 0x6f, 0x63, 0x6b, 0x4c, 0x61, 0x72, 0x67, 0x65, 0x10, 0x03, 0x2a,
-	0x37, 0x0a, 0x04, 0x54, 0x69, 0x6c, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x69, 0x6c, 0x65, 0x55,
-	0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x6f, 0x63, 0x6b,
-	0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x47, 0x72, 0x61, 0x76, 0x65, 0x6c, 0x10, 0x02, 0x12, 0x08,
-	0x0a, 0x04, 0x53, 0x61, 0x6e, 0x64, 0x10, 0x03, 0x32, 0xcf, 0x02, 0x0a, 0x04, 0x52, 0x6f, 0x76,
-	0x65, 0x12, 0x4d, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75,
-	0x73, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76,
-	0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
-	0x1d, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
-	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
-	0x12, 0x41, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72,
-	0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69,
-	0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x17,
-	0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70,
-	0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x05, 0x52, 0x61, 0x64, 0x61, 0x72, 0x12, 0x15, 0x2e, 0x72,
-	0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61,
-	0x64, 0x61, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a,
-	0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70,
-	0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
-	0x17, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69,
-	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x64, 0x69, 0x6c, 0x75, 0x7a, 0x2f,
-	0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x76, 0x65, 0x61,
-	0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x6e, 0x74, 0x22, 0x75, 0x0a, 0x0d, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x05, 0x52, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x74, 0x69, 0x6c,
+	0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61,
+	0x70, 0x69, 0x2e, 0x54, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x74, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x29,
+	0x0a, 0x07, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32,
+	0x0f, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74,
+	0x52, 0x07, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x22, 0x3b, 0x0a, 0x0d, 0x53, 0x74, 0x61,
+	0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x61, 0x63,
+	0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f,
+	0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61,
+	0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x2d, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x12, 0x0a,
+	0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x69, 0x6d,
+	0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0x24, 0x0a, 0x06, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12,
+	0x0c, 0x0a, 0x01, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x78, 0x12, 0x0c, 0x0a,
+	0x01, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x79, 0x22, 0xc3, 0x03, 0x0a, 0x0e,
+	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12,
+	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x56,
+	0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12,
+	0x14, 0x0a, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05,
+	0x72, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f,
+	0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74,
+	0x6f, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18,
+	0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12,
+	0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01,
+	0x28, 0x05, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2a, 0x0a,
+	0x10, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74,
+	0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d,
+	0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61,
+	0x72, 0x67, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x63, 0x68, 0x61, 0x72, 0x67,
+	0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x72,
+	0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75,
+	0x6d, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, 0x3c, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d,
+	0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d,
+	0x61, 0x6e, 0x64, 0x52, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d,
+	0x6d, 0x61, 0x6e, 0x64, 0x73, 0x12, 0x38, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x43,
+	0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e,
+	0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52,
+	0x0e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x12,
+	0x20, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e,
+	0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67,
+	0x73, 0x2a, 0x55, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x54, 0x79, 0x70, 0x65,
+	0x12, 0x08, 0x0a, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x6d, 0x6f,
+	0x76, 0x65, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x73, 0x68, 0x10, 0x02, 0x12,
+	0x0a, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x61, 0x69, 0x72, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x72,
+	0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x62, 0x72, 0x6f,
+	0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x10, 0x05, 0x2a, 0x47, 0x0a, 0x07, 0x42, 0x65, 0x61, 0x72,
+	0x69, 0x6e, 0x67, 0x12, 0x12, 0x0a, 0x0e, 0x42, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x6e,
+	0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x4e, 0x6f, 0x72, 0x74, 0x68,
+	0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x45, 0x61, 0x73, 0x74, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05,
+	0x53, 0x6f, 0x75, 0x74, 0x68, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x65, 0x73, 0x74, 0x10,
+	0x04, 0x2a, 0x48, 0x0a, 0x06, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x11, 0x0a, 0x0d, 0x4f,
+	0x62, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0d,
+	0x0a, 0x09, 0x52, 0x6f, 0x76, 0x65, 0x72, 0x4c, 0x69, 0x76, 0x65, 0x10, 0x01, 0x12, 0x0d, 0x0a,
+	0x09, 0x52, 0x6f, 0x63, 0x6b, 0x53, 0x6d, 0x61, 0x6c, 0x6c, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09,
+	0x52, 0x6f, 0x63, 0x6b, 0x4c, 0x61, 0x72, 0x67, 0x65, 0x10, 0x03, 0x2a, 0x37, 0x0a, 0x04, 0x54,
+	0x69, 0x6c, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x69, 0x6c, 0x65, 0x55, 0x6e, 0x6b, 0x6e, 0x6f,
+	0x77, 0x6e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x6f, 0x63, 0x6b, 0x10, 0x01, 0x12, 0x0a,
+	0x0a, 0x06, 0x47, 0x72, 0x61, 0x76, 0x65, 0x6c, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x61,
+	0x6e, 0x64, 0x10, 0x03, 0x32, 0xcf, 0x02, 0x0a, 0x04, 0x52, 0x6f, 0x76, 0x65, 0x12, 0x4d, 0x0a,
+	0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x2e,
+	0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74,
+	0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f,
+	0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
+	0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x08,
+	0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61,
+	0x70, 0x69, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x67,
+	0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
+	0x3e, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x17, 0x2e, 0x72, 0x6f, 0x76,
+	0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f,
+	0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
+	0x38, 0x0a, 0x05, 0x52, 0x61, 0x64, 0x61, 0x72, 0x12, 0x15, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61,
+	0x70, 0x69, 0x2e, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+	0x16, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x53, 0x74, 0x61,
+	0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74,
+	0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x72, 0x6f,
+	0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70,
+	0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
+	0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x64, 0x69, 0x6c, 0x75, 0x7a, 0x2f, 0x72, 0x6f, 0x76, 0x65,
+	0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x62, 0x06,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -1254,55 +1316,57 @@ func file_roveapi_roveapi_proto_rawDescGZIP() []byte {
 	return file_roveapi_roveapi_proto_rawDescData
 }
 
-var file_roveapi_roveapi_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
+var file_roveapi_roveapi_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
 var file_roveapi_roveapi_proto_msgTypes = make([]protoimpl.MessageInfo, 14)
 var file_roveapi_roveapi_proto_goTypes = []interface{}{
 	(CommandType)(0),             // 0: roveapi.CommandType
-	(Object)(0),                  // 1: roveapi.Object
-	(Tile)(0),                    // 2: roveapi.Tile
-	(*ServerStatusRequest)(nil),  // 3: roveapi.ServerStatusRequest
-	(*ServerStatusResponse)(nil), // 4: roveapi.ServerStatusResponse
-	(*RegisterRequest)(nil),      // 5: roveapi.RegisterRequest
-	(*Account)(nil),              // 6: roveapi.Account
-	(*RegisterResponse)(nil),     // 7: roveapi.RegisterResponse
-	(*Command)(nil),              // 8: roveapi.Command
-	(*CommandRequest)(nil),       // 9: roveapi.CommandRequest
-	(*CommandResponse)(nil),      // 10: roveapi.CommandResponse
-	(*RadarRequest)(nil),         // 11: roveapi.RadarRequest
-	(*RadarResponse)(nil),        // 12: roveapi.RadarResponse
-	(*StatusRequest)(nil),        // 13: roveapi.StatusRequest
-	(*Log)(nil),                  // 14: roveapi.Log
-	(*Vector)(nil),               // 15: roveapi.Vector
-	(*StatusResponse)(nil),       // 16: roveapi.StatusResponse
+	(Bearing)(0),                 // 1: roveapi.Bearing
+	(Object)(0),                  // 2: roveapi.Object
+	(Tile)(0),                    // 3: roveapi.Tile
+	(*ServerStatusRequest)(nil),  // 4: roveapi.ServerStatusRequest
+	(*ServerStatusResponse)(nil), // 5: roveapi.ServerStatusResponse
+	(*RegisterRequest)(nil),      // 6: roveapi.RegisterRequest
+	(*Account)(nil),              // 7: roveapi.Account
+	(*RegisterResponse)(nil),     // 8: roveapi.RegisterResponse
+	(*Command)(nil),              // 9: roveapi.Command
+	(*CommandRequest)(nil),       // 10: roveapi.CommandRequest
+	(*CommandResponse)(nil),      // 11: roveapi.CommandResponse
+	(*RadarRequest)(nil),         // 12: roveapi.RadarRequest
+	(*RadarResponse)(nil),        // 13: roveapi.RadarResponse
+	(*StatusRequest)(nil),        // 14: roveapi.StatusRequest
+	(*Log)(nil),                  // 15: roveapi.Log
+	(*Vector)(nil),               // 16: roveapi.Vector
+	(*StatusResponse)(nil),       // 17: roveapi.StatusResponse
 }
 var file_roveapi_roveapi_proto_depIdxs = []int32{
-	6,  // 0: roveapi.RegisterResponse.account:type_name -> roveapi.Account
+	7,  // 0: roveapi.RegisterResponse.account:type_name -> roveapi.Account
 	0,  // 1: roveapi.Command.command:type_name -> roveapi.CommandType
-	6,  // 2: roveapi.CommandRequest.account:type_name -> roveapi.Account
-	8,  // 3: roveapi.CommandRequest.commands:type_name -> roveapi.Command
-	6,  // 4: roveapi.RadarRequest.account:type_name -> roveapi.Account
-	2,  // 5: roveapi.RadarResponse.tiles:type_name -> roveapi.Tile
-	1,  // 6: roveapi.RadarResponse.objects:type_name -> roveapi.Object
-	6,  // 7: roveapi.StatusRequest.account:type_name -> roveapi.Account
-	15, // 8: roveapi.StatusResponse.position:type_name -> roveapi.Vector
-	8,  // 9: roveapi.StatusResponse.incomingCommands:type_name -> roveapi.Command
-	8,  // 10: roveapi.StatusResponse.queuedCommands:type_name -> roveapi.Command
-	14, // 11: roveapi.StatusResponse.logs:type_name -> roveapi.Log
-	3,  // 12: roveapi.Rove.ServerStatus:input_type -> roveapi.ServerStatusRequest
-	5,  // 13: roveapi.Rove.Register:input_type -> roveapi.RegisterRequest
-	9,  // 14: roveapi.Rove.Command:input_type -> roveapi.CommandRequest
-	11, // 15: roveapi.Rove.Radar:input_type -> roveapi.RadarRequest
-	13, // 16: roveapi.Rove.Status:input_type -> roveapi.StatusRequest
-	4,  // 17: roveapi.Rove.ServerStatus:output_type -> roveapi.ServerStatusResponse
-	7,  // 18: roveapi.Rove.Register:output_type -> roveapi.RegisterResponse
-	10, // 19: roveapi.Rove.Command:output_type -> roveapi.CommandResponse
-	12, // 20: roveapi.Rove.Radar:output_type -> roveapi.RadarResponse
-	16, // 21: roveapi.Rove.Status:output_type -> roveapi.StatusResponse
-	17, // [17:22] is the sub-list for method output_type
-	12, // [12:17] is the sub-list for method input_type
-	12, // [12:12] is the sub-list for extension type_name
-	12, // [12:12] is the sub-list for extension extendee
-	0,  // [0:12] is the sub-list for field type_name
+	1,  // 2: roveapi.Command.bearing:type_name -> roveapi.Bearing
+	7,  // 3: roveapi.CommandRequest.account:type_name -> roveapi.Account
+	9,  // 4: roveapi.CommandRequest.commands:type_name -> roveapi.Command
+	7,  // 5: roveapi.RadarRequest.account:type_name -> roveapi.Account
+	3,  // 6: roveapi.RadarResponse.tiles:type_name -> roveapi.Tile
+	2,  // 7: roveapi.RadarResponse.objects:type_name -> roveapi.Object
+	7,  // 8: roveapi.StatusRequest.account:type_name -> roveapi.Account
+	16, // 9: roveapi.StatusResponse.position:type_name -> roveapi.Vector
+	9,  // 10: roveapi.StatusResponse.incomingCommands:type_name -> roveapi.Command
+	9,  // 11: roveapi.StatusResponse.queuedCommands:type_name -> roveapi.Command
+	15, // 12: roveapi.StatusResponse.logs:type_name -> roveapi.Log
+	4,  // 13: roveapi.Rove.ServerStatus:input_type -> roveapi.ServerStatusRequest
+	6,  // 14: roveapi.Rove.Register:input_type -> roveapi.RegisterRequest
+	10, // 15: roveapi.Rove.Command:input_type -> roveapi.CommandRequest
+	12, // 16: roveapi.Rove.Radar:input_type -> roveapi.RadarRequest
+	14, // 17: roveapi.Rove.Status:input_type -> roveapi.StatusRequest
+	5,  // 18: roveapi.Rove.ServerStatus:output_type -> roveapi.ServerStatusResponse
+	8,  // 19: roveapi.Rove.Register:output_type -> roveapi.RegisterResponse
+	11, // 20: roveapi.Rove.Command:output_type -> roveapi.CommandResponse
+	13, // 21: roveapi.Rove.Radar:output_type -> roveapi.RadarResponse
+	17, // 22: roveapi.Rove.Status:output_type -> roveapi.StatusResponse
+	18, // [18:23] is the sub-list for method output_type
+	13, // [13:18] is the sub-list for method input_type
+	13, // [13:13] is the sub-list for extension type_name
+	13, // [13:13] is the sub-list for extension extendee
+	0,  // [0:13] is the sub-list for field type_name
 }
 
 func init() { file_roveapi_roveapi_proto_init() }
@@ -1489,7 +1553,7 @@ func file_roveapi_roveapi_proto_init() {
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_roveapi_roveapi_proto_rawDesc,
-			NumEnums:      3,
+			NumEnums:      4,
 			NumMessages:   14,
 			NumExtensions: 0,
 			NumServices:   1,
diff --git a/proto/roveapi/roveapi.proto b/proto/roveapi/roveapi.proto
index 680aa9a..b43087b 100644
--- a/proto/roveapi/roveapi.proto
+++ b/proto/roveapi/roveapi.proto
@@ -101,15 +101,24 @@ enum CommandType {
   broadcast = 5;
 }
 
+enum Bearing {
+  // BearingUnknown an unknown invalid bearing
+  BearingUnknown = 0;
+  North = 1;
+  East = 2;
+  South = 3;
+  West = 4;
+}
+
 // Command is a single command for a rover
 message Command {
   // The command type
   CommandType command = 1;
 
   oneof data {
-    // A bearing, example: NE
+    // A bearing
     // Used with MOVE
-    string bearing = 2;
+    Bearing bearing = 2;
 
     // A simple message, must be composed of printable ASCII glyphs (32-126)
     // maximum of three characters

From 57f668ae548ab28745e11afec08df78820b7e42d Mon Sep 17 00:00:00 2001
From: Marc Di Luzio <marc.diluzio@gmail.com>
Date: Sun, 19 Jul 2020 13:13:09 +0100
Subject: [PATCH 2/2] Reinstate BearingFromString function

---
 cmd/rove/main.go  | 24 ++++++++++++++++++++----
 pkg/rove/world.go |  2 +-
 2 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/cmd/rove/main.go b/cmd/rove/main.go
index 4e3a1d2..bd98d52 100644
--- a/cmd/rove/main.go
+++ b/cmd/rove/main.go
@@ -11,7 +11,6 @@ import (
 	"time"
 
 	"github.com/mdiluz/rove/cmd/rove/internal"
-	"github.com/mdiluz/rove/pkg/maths"
 	"github.com/mdiluz/rove/pkg/version"
 	"github.com/mdiluz/rove/proto/roveapi"
 	"golang.org/x/net/context"
@@ -124,6 +123,21 @@ func checkAccount(a Account) error {
 	return nil
 }
 
+// BearingFromString converts a string to a bearing
+func BearingFromString(s string) roveapi.Bearing {
+	switch s {
+	case "N":
+		return roveapi.Bearing_North
+	case "E":
+		return roveapi.Bearing_East
+	case "S":
+		return roveapi.Bearing_South
+	case "W":
+		return roveapi.Bearing_West
+	}
+	return roveapi.Bearing_BearingUnknown
+}
+
 // InnerMain wraps the main function so we can test it
 func InnerMain(command string, args ...string) error {
 
@@ -215,13 +229,15 @@ func InnerMain(command string, args ...string) error {
 				i++
 				if len(args) == i {
 					return fmt.Errorf("move command must be passed bearing")
-				} else if _, err := maths.BearingFromString(args[i]); err != nil {
-					return err
+				}
+				var b roveapi.Bearing
+				if b = BearingFromString(args[i]); b == roveapi.Bearing_BearingUnknown {
+					return fmt.Errorf("unrecognised bearing: %s", args[i])
 				}
 				commands = append(commands,
 					&roveapi.Command{
 						Command: roveapi.CommandType_move,
-						Data:    &roveapi.Command_Bearing{Bearing: args[i]},
+						Data:    &roveapi.Command_Bearing{Bearing: b},
 					},
 				)
 			case "broadcast":
diff --git a/pkg/rove/world.go b/pkg/rove/world.go
index a5ab05f..7c313ba 100644
--- a/pkg/rove/world.go
+++ b/pkg/rove/world.go
@@ -427,7 +427,7 @@ func (w *World) Enqueue(rover string, commands ...Command) error {
 		switch c.Command {
 		case roveapi.CommandType_move:
 			if c.Bearing == roveapi.Bearing_BearingUnknown {
-				return fmt.Errorf("bearing must be cardinal")
+				return fmt.Errorf("bearing must be valid")
 			}
 		case roveapi.CommandType_broadcast:
 			if len(c.Message) > 3 {