Add SailPosition to the rover and implement toggle command

This also converts the commands to use the proto type for simplicity
This commit is contained in:
Marc Di Luzio 2020-07-21 23:12:50 +01:00
parent 6f30b665c7
commit f78efd1223
10 changed files with 490 additions and 267 deletions

View file

@ -89,12 +89,20 @@ func BearingToVector(b roveapi.Bearing) Vector {
switch b {
case roveapi.Bearing_North:
return Vector{Y: 1}
case roveapi.Bearing_NorthEast:
return Vector{X: 1, Y: 1}
case roveapi.Bearing_East:
return Vector{X: 1}
case roveapi.Bearing_SouthEast:
return Vector{X: 1, Y: -1}
case roveapi.Bearing_South:
return Vector{Y: -1}
case roveapi.Bearing_SouthWest:
return Vector{X: -1, Y: -1}
case roveapi.Bearing_West:
return Vector{X: -1}
case roveapi.Bearing_NorthWest:
return Vector{X: -1, Y: 1}
}
return Vector{}

View file

@ -1,14 +0,0 @@
package rove
import "github.com/mdiluz/rove/proto/roveapi"
// Command represends a single command to execute
type Command struct {
Command roveapi.CommandType `json:"command"`
// Used in the broadcast command
Message []byte `json:"message,omitempty"`
}
// CommandStream is a list of commands to execute in order
type CommandStream []Command

View file

@ -2,14 +2,49 @@ package rove
import (
"testing"
"github.com/mdiluz/rove/proto/roveapi"
"github.com/stretchr/testify/assert"
)
func TestCommand_Raise(t *testing.T) {
// TODO: Test the raise command
func TestCommand_Toggle(t *testing.T) {
w := NewWorld(8)
a, err := w.SpawnRover()
assert.NoError(t, err)
r, err := w.GetRover(a)
assert.NoError(t, err)
assert.Equal(t, roveapi.SailPosition_SolarCharging, r.SailPosition)
w.Enqueue(a, &roveapi.Command{Command: roveapi.CommandType_toggle})
w.EnqueueAllIncoming()
w.ExecuteCommandQueues()
r, err = w.GetRover(a)
assert.NoError(t, err)
assert.Equal(t, roveapi.SailPosition_CatchingWind, r.SailPosition)
w.Enqueue(a, &roveapi.Command{Command: roveapi.CommandType_toggle})
w.EnqueueAllIncoming()
w.ExecuteCommandQueues()
r, err = w.GetRover(a)
assert.NoError(t, err)
assert.Equal(t, roveapi.SailPosition_SolarCharging, r.SailPosition)
}
func TestCommand_Lower(t *testing.T) {
// TODO: Test the lower command
func TestCommand_Turn(t *testing.T) {
w := NewWorld(8)
a, err := w.SpawnRover()
assert.NoError(t, err)
w.Enqueue(a, &roveapi.Command{Command: roveapi.CommandType_turn, Data: &roveapi.Command_Turn{Turn: roveapi.Bearing_NorthWest}})
w.EnqueueAllIncoming()
w.ExecuteCommandQueues()
r, err := w.GetRover(a)
assert.NoError(t, err)
assert.Equal(t, roveapi.Bearing_NorthWest, r.Bearing)
}
func TestCommand_Stash(t *testing.T) {

View file

@ -10,6 +10,7 @@ import (
"github.com/google/uuid"
"github.com/mdiluz/rove/pkg/maths"
"github.com/mdiluz/rove/proto/roveapi"
)
// RoverLogEntry describes a single log entry for the rover
@ -29,6 +30,9 @@ type Rover struct {
// Pos represents where this rover is in the world
Pos maths.Vector `json:"pos"`
// Bearing is the current direction the rover is facing
Bearing roveapi.Bearing `json:"bearing"`
// Range represents the distance the unit's radar can see
Range int `json:"range"`
@ -48,7 +52,10 @@ type Rover struct {
Charge int `json:"charge"`
// MaximumCharge is the maximum charge able to be stored
MaximumCharge int `json:"maximum-Charge"`
MaximumCharge int `json:"maximum-charge"`
// SailPosition is the current position of the sails
SailPosition roveapi.SailPosition `json:"sail-position"`
// Logs Stores log of information
Logs []RoverLogEntry `json:"logs"`
@ -63,6 +70,8 @@ func DefaultRover() Rover {
Capacity: 10,
Charge: 10,
MaximumCharge: 10,
Bearing: roveapi.Bearing_North,
SailPosition: roveapi.SailPosition_SolarCharging,
Name: GenerateRoverName(),
}
}

View file

@ -10,6 +10,9 @@ import (
"github.com/mdiluz/rove/proto/roveapi"
)
// CommandStream is a list of commands to execute in order
type CommandStream []roveapi.Command
// World describes a self contained universe and everything in it
type World struct {
// TicksPerDay is the amount of ticks in a single day
@ -305,6 +308,45 @@ func (w *World) RoverStash(rover string) (roveapi.Object, error) {
return obj.Type, nil
}
// RoverToggle will toggle the sail position
func (w *World) RoverToggle(rover string) (roveapi.SailPosition, error) {
w.worldMutex.Lock()
defer w.worldMutex.Unlock()
r, ok := w.Rovers[rover]
if !ok {
return roveapi.SailPosition_UnknownSailPosition, fmt.Errorf("no rover matching id")
}
// Swap the sail position
switch r.SailPosition {
case roveapi.SailPosition_CatchingWind:
r.SailPosition = roveapi.SailPosition_SolarCharging
case roveapi.SailPosition_SolarCharging:
r.SailPosition = roveapi.SailPosition_CatchingWind
}
w.Rovers[rover] = r
return r.SailPosition, nil
}
// RoverTurn will turn the rover
func (w *World) RoverTurn(rover string, bearing roveapi.Bearing) (roveapi.Bearing, error) {
w.worldMutex.Lock()
defer w.worldMutex.Unlock()
r, ok := w.Rovers[rover]
if !ok {
return roveapi.Bearing_BearingUnknown, fmt.Errorf("no rover matching id")
}
// Set the new bearing
r.Bearing = bearing
w.Rovers[rover] = r
return r.Bearing, nil
}
// RadarFromRover can be used to query what a rover can currently see
func (w *World) RadarFromRover(rover string) (radar []roveapi.Tile, objs []roveapi.Object, err error) {
w.worldMutex.RLock()
@ -364,7 +406,7 @@ func (w *World) RadarFromRover(rover string) (radar []roveapi.Tile, objs []rovea
}
// RoverCommands returns current commands for the given rover
func (w *World) RoverCommands(rover string) (incoming []Command, queued []Command) {
func (w *World) RoverCommands(rover string) (incoming []roveapi.Command, queued []roveapi.Command) {
if c, ok := w.CommandIncoming[rover]; ok {
incoming = c
}
@ -375,20 +417,24 @@ func (w *World) RoverCommands(rover string) (incoming []Command, queued []Comman
}
// Enqueue will queue the commands given
func (w *World) Enqueue(rover string, commands ...Command) error {
func (w *World) Enqueue(rover string, commands ...*roveapi.Command) error {
// First validate the commands
for _, c := range commands {
switch c.Command {
case roveapi.CommandType_broadcast:
if len(c.Message) > 3 {
return fmt.Errorf("too many characters in message (limit 3): %d", len(c.Message))
if len(c.GetBroadcast()) > 3 {
return fmt.Errorf("too many characters in message (limit 3): %d", len(c.GetBroadcast()))
}
for _, b := range c.Message {
for _, b := range c.GetBroadcast() {
if b < 37 || b > 126 {
return fmt.Errorf("invalid message character: %c", b)
}
}
case roveapi.CommandType_turn:
if c.GetTurn() == roveapi.Bearing_BearingUnknown {
return fmt.Errorf("turn command given unknown bearing")
}
case roveapi.CommandType_toggle:
case roveapi.CommandType_stash:
case roveapi.CommandType_repair:
@ -403,7 +449,17 @@ func (w *World) Enqueue(rover string, commands ...Command) error {
defer w.cmdMutex.Unlock()
// Override the incoming command set
w.CommandIncoming[rover] = commands
var cmds []roveapi.Command
for _, c := range commands {
// Copy the command and data but none of the locks or internals
cmds = append(cmds,
roveapi.Command{
Command: c.Command,
Data: c.Data,
})
}
w.CommandIncoming[rover] = cmds
return nil
}
@ -427,16 +483,16 @@ func (w *World) ExecuteCommandQueues() {
// Iterate through all the current commands
for rover, cmds := range w.CommandQueue {
if len(cmds) != 0 {
// Extract the first command in the queue
c := cmds[0]
w.CommandQueue[rover] = cmds[1:]
// Execute the command
if err := w.ExecuteCommand(&c, rover); err != nil {
if err := w.ExecuteCommand(&cmds[0], rover); err != nil {
log.Println(err)
// TODO: Report this error somehow
}
// Extract the first command in the queue
w.CommandQueue[rover] = cmds[1:]
} else {
// Clean out the empty entry
delete(w.CommandQueue, rover)
@ -451,13 +507,14 @@ func (w *World) ExecuteCommandQueues() {
}
// ExecuteCommand will execute a single command
func (w *World) ExecuteCommand(c *Command, rover string) (err error) {
log.Printf("Executing command: %+v for %s\n", *c, rover)
func (w *World) ExecuteCommand(c *roveapi.Command, rover string) (err error) {
log.Printf("Executing command: %+v for %s\n", c.Command, rover)
switch c.Command {
case roveapi.CommandType_toggle:
// TODO: Toggle the sails
if _, err := w.RoverToggle(rover); err != nil {
return err
}
case roveapi.CommandType_stash:
if _, err := w.RoverStash(rover); err != nil {
return err
@ -477,7 +534,12 @@ func (w *World) ExecuteCommand(c *Command, rover string) (err error) {
}
case roveapi.CommandType_broadcast:
if err := w.RoverBroadcast(rover, c.Message); err != nil {
if err := w.RoverBroadcast(rover, c.GetBroadcast()); err != nil {
return err
}
case roveapi.CommandType_turn:
if _, err := w.RoverTurn(rover, c.GetTurn()); err != nil {
return err
}

View file

@ -269,7 +269,7 @@ func TestWorld_RoverRepair(t *testing.T) {
assert.NoError(t, err, "couldn't get rover info")
assert.Equal(t, originalInfo.Integrity-1, newinfo.Integrity, "rover should have lost integrity")
err = world.ExecuteCommand(&Command{Command: roveapi.CommandType_repair}, a)
err = world.ExecuteCommand(&roveapi.Command{Command: roveapi.CommandType_repair}, a)
assert.NoError(t, err, "Failed to repair rover")
newinfo, err = world.GetRover(a)
@ -283,7 +283,7 @@ func TestWorld_RoverRepair(t *testing.T) {
assert.NoError(t, err, "Failed to stash")
assert.Equal(t, roveapi.Object_RockSmall, o, "Failed to get correct object")
err = world.ExecuteCommand(&Command{Command: roveapi.CommandType_repair}, a)
err = world.ExecuteCommand(&roveapi.Command{Command: roveapi.CommandType_repair}, a)
assert.NoError(t, err, "Failed to repair rover")
newinfo, err = world.GetRover(a)