Rename generated rove package to roveapi and the game package to rove
This commit is contained in:
parent
b451ea519d
commit
b534ac0516
12 changed files with 334 additions and 329 deletions
17
pkg/rove/command.go
Normal file
17
pkg/rove/command.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package rove
|
||||
|
||||
import "github.com/mdiluz/rove/pkg/roveapi"
|
||||
|
||||
// Command represends a single command to execute
|
||||
type Command struct {
|
||||
Command roveapi.CommandType `json:"command"`
|
||||
|
||||
// Used in the move command
|
||||
Bearing string `json:"bearing,omitempty"`
|
||||
|
||||
// Used in the broadcast command
|
||||
Message []byte `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// CommandStream is a list of commands to execute in order
|
||||
type CommandStream []Command
|
70
pkg/rove/command_test.go
Normal file
70
pkg/rove/command_test.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package rove
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mdiluz/rove/pkg/roveapi"
|
||||
"github.com/mdiluz/rove/pkg/vector"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommand_Move(t *testing.T) {
|
||||
world := NewWorld(8)
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
pos := vector.Vector{
|
||||
X: 1.0,
|
||||
Y: 2.0,
|
||||
}
|
||||
|
||||
err = world.WarpRover(a, pos)
|
||||
assert.NoError(t, err, "Failed to set position for rover")
|
||||
|
||||
// Try the move command
|
||||
moveCommand := Command{Command: roveapi.CommandType_move, Bearing: "N"}
|
||||
assert.NoError(t, world.Enqueue(a, moveCommand), "Failed to execute move command")
|
||||
|
||||
// Tick the world
|
||||
world.EnqueueAllIncoming()
|
||||
world.ExecuteCommandQueues()
|
||||
|
||||
newPos, err := world.RoverPosition(a)
|
||||
assert.NoError(t, err, "Failed to set position for rover")
|
||||
pos.Add(vector.Vector{X: 0.0, Y: 1})
|
||||
assert.Equal(t, pos, newPos, "Failed to correctly set position for rover")
|
||||
}
|
||||
|
||||
func TestCommand_Recharge(t *testing.T) {
|
||||
world := NewWorld(8)
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
pos := vector.Vector{
|
||||
X: 1.0,
|
||||
Y: 2.0,
|
||||
}
|
||||
|
||||
err = world.WarpRover(a, pos)
|
||||
assert.NoError(t, err, "Failed to set position for rover")
|
||||
|
||||
// Move to use up some charge
|
||||
moveCommand := Command{Command: roveapi.CommandType_move, Bearing: "N"}
|
||||
assert.NoError(t, world.Enqueue(a, moveCommand), "Failed to queue move command")
|
||||
|
||||
// Tick the world
|
||||
world.EnqueueAllIncoming()
|
||||
world.ExecuteCommandQueues()
|
||||
|
||||
rover, _ := world.GetRover(a)
|
||||
assert.Equal(t, rover.MaximumCharge-1, rover.Charge)
|
||||
|
||||
chargeCommand := Command{Command: roveapi.CommandType_recharge}
|
||||
assert.NoError(t, world.Enqueue(a, chargeCommand), "Failed to queue recharge command")
|
||||
|
||||
// Tick the world
|
||||
world.EnqueueAllIncoming()
|
||||
world.ExecuteCommandQueues()
|
||||
|
||||
rover, _ = world.GetRover(a)
|
||||
assert.Equal(t, rover.MaximumCharge, rover.Charge)
|
||||
|
||||
}
|
1622
pkg/rove/rove.pb.go
1622
pkg/rove/rove.pb.go
File diff suppressed because it is too large
Load diff
64
pkg/rove/rover.go
Normal file
64
pkg/rove/rover.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package rove
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/mdiluz/rove/pkg/objects"
|
||||
"github.com/mdiluz/rove/pkg/vector"
|
||||
)
|
||||
|
||||
// RoverLogEntry describes a single log entry for the rover
|
||||
type RoverLogEntry struct {
|
||||
// Time is the timestamp of the entry
|
||||
Time time.Time `json:"time"`
|
||||
|
||||
// Text contains the information in this log entry
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// Rover describes a single rover in the world
|
||||
type Rover struct {
|
||||
// Unique name of this rover
|
||||
Name string `json:"name"`
|
||||
|
||||
// Pos represents where this rover is in the world
|
||||
Pos vector.Vector `json:"pos"`
|
||||
|
||||
// Range represents the distance the unit's radar can see
|
||||
Range int `json:"range"`
|
||||
|
||||
// Inventory represents any items the rover is carrying
|
||||
Inventory []objects.Object `json:"inventory"`
|
||||
|
||||
// Capacity is the maximum number of inventory items
|
||||
Capacity int `json:"capacity"`
|
||||
|
||||
// Integrity represents current rover health
|
||||
Integrity int `json:"integrity"`
|
||||
|
||||
// MaximumIntegrity is the full integrity of the rover
|
||||
MaximumIntegrity int `json:"maximum-integrity"`
|
||||
|
||||
// Charge is the amount of energy the rover has
|
||||
Charge int `json:"charge"`
|
||||
|
||||
// MaximumCharge is the maximum charge able to be stored
|
||||
MaximumCharge int `json:"maximum-Charge"`
|
||||
|
||||
// Logs Stores log of information
|
||||
Logs []RoverLogEntry `json:"logs"`
|
||||
}
|
||||
|
||||
// AddLogEntryf adds an entry to the rovers log
|
||||
func (r *Rover) AddLogEntryf(format string, args ...interface{}) {
|
||||
text := fmt.Sprintf(format, args...)
|
||||
log.Printf("%s log entry: %s", r.Name, text)
|
||||
r.Logs = append(r.Logs,
|
||||
RoverLogEntry{
|
||||
Time: time.Now(),
|
||||
Text: text,
|
||||
},
|
||||
)
|
||||
}
|
581
pkg/rove/world.go
Normal file
581
pkg/rove/world.go
Normal file
|
@ -0,0 +1,581 @@
|
|||
package rove
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/mdiluz/rove/pkg/atlas"
|
||||
"github.com/mdiluz/rove/pkg/bearing"
|
||||
"github.com/mdiluz/rove/pkg/objects"
|
||||
"github.com/mdiluz/rove/pkg/roveapi"
|
||||
"github.com/mdiluz/rove/pkg/vector"
|
||||
)
|
||||
|
||||
// World describes a self contained universe and everything in it
|
||||
type World struct {
|
||||
// Rovers is a id->data map of all the rovers in the game
|
||||
Rovers map[string]Rover `json:"rovers"`
|
||||
|
||||
// Atlas represends the world map of chunks and tiles
|
||||
Atlas atlas.Atlas `json:"atlas"`
|
||||
|
||||
// Mutex to lock around all world operations
|
||||
worldMutex sync.RWMutex
|
||||
|
||||
// Commands is the set of currently executing command streams per rover
|
||||
CommandQueue map[string]CommandStream `json:"commands"`
|
||||
|
||||
// Incoming represents the set of commands to add to the queue at the end of the current tick
|
||||
CommandIncoming map[string]CommandStream `json:"incoming"`
|
||||
|
||||
// Mutex to lock around command operations
|
||||
cmdMutex sync.RWMutex
|
||||
|
||||
// Set of possible words to use for names
|
||||
words []string
|
||||
|
||||
// TicksPerDay is the amount of ticks in a single day
|
||||
TicksPerDay int `json:"ticks-per-day"`
|
||||
|
||||
// Current number of ticks from the start
|
||||
CurrentTicks int `json:"current-ticks"`
|
||||
}
|
||||
|
||||
var wordsFile = os.Getenv("WORDS_FILE")
|
||||
|
||||
// NewWorld creates a new world object
|
||||
func NewWorld(chunkSize int) *World {
|
||||
|
||||
// Try and load the words file
|
||||
var lines []string
|
||||
if file, err := os.Open(wordsFile); err != nil {
|
||||
log.Printf("Couldn't read words file [%s], running without words: %s\n", wordsFile, err)
|
||||
} else {
|
||||
defer file.Close()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
if scanner.Err() != nil {
|
||||
log.Printf("Failure during word file scan: %s\n", scanner.Err())
|
||||
}
|
||||
}
|
||||
|
||||
return &World{
|
||||
Rovers: make(map[string]Rover),
|
||||
CommandQueue: make(map[string]CommandStream),
|
||||
CommandIncoming: make(map[string]CommandStream),
|
||||
Atlas: atlas.NewChunkAtlas(chunkSize),
|
||||
words: lines,
|
||||
TicksPerDay: 24,
|
||||
CurrentTicks: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// SpawnRover adds an rover to the game
|
||||
func (w *World) SpawnRover() (string, error) {
|
||||
w.worldMutex.Lock()
|
||||
defer w.worldMutex.Unlock()
|
||||
|
||||
// Initialise the rover
|
||||
rover := Rover{
|
||||
Range: 4,
|
||||
Integrity: 10,
|
||||
MaximumIntegrity: 10,
|
||||
Capacity: 10,
|
||||
Charge: 10,
|
||||
MaximumCharge: 10,
|
||||
Name: uuid.New().String(),
|
||||
}
|
||||
|
||||
// Assign a random name if we have words
|
||||
if len(w.words) > 0 {
|
||||
for {
|
||||
// Loop until we find a unique name
|
||||
name := fmt.Sprintf("%s-%s", w.words[rand.Intn(len(w.words))], w.words[rand.Intn(len(w.words))])
|
||||
if _, ok := w.Rovers[name]; !ok {
|
||||
rover.Name = name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn in a random place near the origin
|
||||
rover.Pos = vector.Vector{
|
||||
X: 10 - rand.Intn(20),
|
||||
Y: 10 - rand.Intn(20),
|
||||
}
|
||||
|
||||
// Seach until we error (run out of world)
|
||||
for {
|
||||
_, obj := w.Atlas.QueryPosition(rover.Pos)
|
||||
if !obj.IsBlocking() {
|
||||
break
|
||||
} else {
|
||||
// Try and spawn to the east of the blockage
|
||||
rover.Pos.Add(vector.Vector{X: 1, Y: 0})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add a log entry for robot creation
|
||||
rover.AddLogEntryf("created at %+v", rover.Pos)
|
||||
|
||||
// Append the rover to the list
|
||||
w.Rovers[rover.Name] = rover
|
||||
|
||||
return rover.Name, nil
|
||||
}
|
||||
|
||||
// GetRover gets a specific rover by name
|
||||
func (w *World) GetRover(rover string) (Rover, error) {
|
||||
w.worldMutex.RLock()
|
||||
defer w.worldMutex.RUnlock()
|
||||
|
||||
i, ok := w.Rovers[rover]
|
||||
if !ok {
|
||||
return Rover{}, fmt.Errorf("Failed to find rover with name: %s", rover)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// RoverRecharge charges up a rover
|
||||
func (w *World) RoverRecharge(rover string) (int, error) {
|
||||
w.worldMutex.Lock()
|
||||
defer w.worldMutex.Unlock()
|
||||
|
||||
i, ok := w.Rovers[rover]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("Failed to find rover with name: %s", rover)
|
||||
}
|
||||
|
||||
// We can only recharge during the day
|
||||
if !w.Daytime() {
|
||||
return i.Charge, nil
|
||||
}
|
||||
|
||||
// Add one charge
|
||||
if i.Charge < i.MaximumCharge {
|
||||
i.Charge++
|
||||
i.AddLogEntryf("recharged to %d", i.Charge)
|
||||
}
|
||||
w.Rovers[rover] = i
|
||||
|
||||
return i.Charge, nil
|
||||
}
|
||||
|
||||
// RoverBroadcast broadcasts a message to nearby rovers
|
||||
func (w *World) RoverBroadcast(rover string, message []byte) (err error) {
|
||||
w.worldMutex.Lock()
|
||||
defer w.worldMutex.Unlock()
|
||||
|
||||
i, ok := w.Rovers[rover]
|
||||
if !ok {
|
||||
return fmt.Errorf("Failed to find rover with name: %s", rover)
|
||||
}
|
||||
|
||||
// Use up a charge as needed, if available
|
||||
if i.Charge == 0 {
|
||||
return
|
||||
}
|
||||
i.Charge--
|
||||
|
||||
// Check all rovers
|
||||
for r, rover := range w.Rovers {
|
||||
if rover.Name == i.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this rover is within range
|
||||
if i.Pos.Distance(rover.Pos) < float64(i.Range) {
|
||||
rover.AddLogEntryf("recieved %s from %s", string(message), i.Name)
|
||||
w.Rovers[r] = rover
|
||||
}
|
||||
}
|
||||
|
||||
i.AddLogEntryf("broadcasted %s", string(message))
|
||||
w.Rovers[rover] = i
|
||||
return
|
||||
}
|
||||
|
||||
// DestroyRover Removes an rover from the game
|
||||
func (w *World) DestroyRover(rover string) error {
|
||||
w.worldMutex.Lock()
|
||||
defer w.worldMutex.Unlock()
|
||||
|
||||
_, ok := w.Rovers[rover]
|
||||
if !ok {
|
||||
return fmt.Errorf("no rover matching id")
|
||||
}
|
||||
|
||||
delete(w.Rovers, rover)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RoverPosition returns the position of the rover
|
||||
func (w *World) RoverPosition(rover string) (vector.Vector, error) {
|
||||
w.worldMutex.RLock()
|
||||
defer w.worldMutex.RUnlock()
|
||||
|
||||
i, ok := w.Rovers[rover]
|
||||
if !ok {
|
||||
return vector.Vector{}, fmt.Errorf("no rover matching id")
|
||||
}
|
||||
return i.Pos, nil
|
||||
}
|
||||
|
||||
// SetRoverPosition sets the position of the rover
|
||||
func (w *World) SetRoverPosition(rover string, pos vector.Vector) error {
|
||||
w.worldMutex.Lock()
|
||||
defer w.worldMutex.Unlock()
|
||||
|
||||
i, ok := w.Rovers[rover]
|
||||
if !ok {
|
||||
return fmt.Errorf("no rover matching id")
|
||||
}
|
||||
|
||||
i.Pos = pos
|
||||
w.Rovers[rover] = i
|
||||
return nil
|
||||
}
|
||||
|
||||
// RoverInventory returns the inventory of a requested rover
|
||||
func (w *World) RoverInventory(rover string) ([]objects.Object, error) {
|
||||
w.worldMutex.RLock()
|
||||
defer w.worldMutex.RUnlock()
|
||||
|
||||
i, ok := w.Rovers[rover]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no rover matching id")
|
||||
}
|
||||
return i.Inventory, nil
|
||||
}
|
||||
|
||||
// WarpRover sets an rovers position
|
||||
func (w *World) WarpRover(rover string, pos vector.Vector) error {
|
||||
w.worldMutex.Lock()
|
||||
defer w.worldMutex.Unlock()
|
||||
|
||||
i, ok := w.Rovers[rover]
|
||||
if !ok {
|
||||
return fmt.Errorf("no rover matching id")
|
||||
}
|
||||
// Nothing to do if these positions match
|
||||
if i.Pos == pos {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check the tile is not blocked
|
||||
_, obj := w.Atlas.QueryPosition(pos)
|
||||
if obj.IsBlocking() {
|
||||
return fmt.Errorf("can't warp rover to occupied tile, check before warping")
|
||||
}
|
||||
|
||||
i.Pos = pos
|
||||
w.Rovers[rover] = i
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveRover attempts to move a rover in a specific direction
|
||||
func (w *World) MoveRover(rover string, b bearing.Bearing) (vector.Vector, error) {
|
||||
w.worldMutex.Lock()
|
||||
defer w.worldMutex.Unlock()
|
||||
|
||||
i, ok := w.Rovers[rover]
|
||||
if !ok {
|
||||
return vector.Vector{}, fmt.Errorf("no rover matching id")
|
||||
}
|
||||
|
||||
// Ensure the rover has energy
|
||||
if i.Charge <= 0 {
|
||||
return i.Pos, nil
|
||||
}
|
||||
i.Charge--
|
||||
|
||||
// Try the new move position
|
||||
newPos := i.Pos.Added(b.Vector())
|
||||
|
||||
// Get the tile and verify it's empty
|
||||
_, obj := w.Atlas.QueryPosition(newPos)
|
||||
if !obj.IsBlocking() {
|
||||
i.AddLogEntryf("moved %s to %+v", b.String(), newPos)
|
||||
// Perform the move
|
||||
i.Pos = newPos
|
||||
w.Rovers[rover] = i
|
||||
} else {
|
||||
// If it is a blocking tile, reduce the rover integrity
|
||||
i.AddLogEntryf("tried to move %s to %+v", b.String(), newPos)
|
||||
i.Integrity = i.Integrity - 1
|
||||
i.AddLogEntryf("had a collision, new integrity %d", i.Integrity)
|
||||
if i.Integrity == 0 {
|
||||
// TODO: The rover needs to be left dormant with the player
|
||||
} else {
|
||||
w.Rovers[rover] = i
|
||||
}
|
||||
}
|
||||
|
||||
return i.Pos, nil
|
||||
}
|
||||
|
||||
// RoverStash will stash an item at the current rovers position
|
||||
func (w *World) RoverStash(rover string) (objects.Type, error) {
|
||||
w.worldMutex.Lock()
|
||||
defer w.worldMutex.Unlock()
|
||||
|
||||
r, ok := w.Rovers[rover]
|
||||
if !ok {
|
||||
return objects.None, fmt.Errorf("no rover matching id")
|
||||
}
|
||||
|
||||
// Can't pick up when full
|
||||
if len(r.Inventory) >= r.Capacity {
|
||||
return objects.None, nil
|
||||
}
|
||||
|
||||
// Ensure the rover has energy
|
||||
if r.Charge <= 0 {
|
||||
return objects.None, nil
|
||||
}
|
||||
r.Charge--
|
||||
|
||||
_, obj := w.Atlas.QueryPosition(r.Pos)
|
||||
if !obj.IsStashable() {
|
||||
return objects.None, nil
|
||||
}
|
||||
|
||||
r.AddLogEntryf("stashed %c", obj.Type)
|
||||
r.Inventory = append(r.Inventory, obj)
|
||||
w.Rovers[rover] = r
|
||||
w.Atlas.SetObject(r.Pos, objects.Object{Type: objects.None})
|
||||
return obj.Type, nil
|
||||
}
|
||||
|
||||
// RadarFromRover can be used to query what a rover can currently see
|
||||
func (w *World) RadarFromRover(rover string) (radar []byte, objs []byte, err error) {
|
||||
w.worldMutex.RLock()
|
||||
defer w.worldMutex.RUnlock()
|
||||
|
||||
r, ok := w.Rovers[rover]
|
||||
if !ok {
|
||||
err = fmt.Errorf("no rover matching id")
|
||||
return
|
||||
}
|
||||
|
||||
// The radar should span in range direction on each axis, plus the row/column the rover is currently on
|
||||
radarSpan := (r.Range * 2) + 1
|
||||
roverPos := r.Pos
|
||||
|
||||
// Get the radar min and max values
|
||||
radarMin := vector.Vector{
|
||||
X: roverPos.X - r.Range,
|
||||
Y: roverPos.Y - r.Range,
|
||||
}
|
||||
radarMax := vector.Vector{
|
||||
X: roverPos.X + r.Range,
|
||||
Y: roverPos.Y + r.Range,
|
||||
}
|
||||
|
||||
// Gather up all tiles within the range
|
||||
radar = make([]byte, radarSpan*radarSpan)
|
||||
objs = make([]byte, radarSpan*radarSpan)
|
||||
for j := radarMin.Y; j <= radarMax.Y; j++ {
|
||||
for i := radarMin.X; i <= radarMax.X; i++ {
|
||||
q := vector.Vector{X: i, Y: j}
|
||||
|
||||
tile, obj := w.Atlas.QueryPosition(q)
|
||||
|
||||
// Get the position relative to the bottom left of the radar
|
||||
relative := q.Added(radarMin.Negated())
|
||||
index := relative.X + relative.Y*radarSpan
|
||||
radar[index] = tile
|
||||
objs[index] = byte(obj.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Add all rovers to the radar
|
||||
for _, r := range w.Rovers {
|
||||
// If the rover is in range
|
||||
dist := r.Pos.Added(roverPos.Negated())
|
||||
dist = dist.Abs()
|
||||
|
||||
if dist.X <= r.Range && dist.Y <= r.Range {
|
||||
relative := r.Pos.Added(radarMin.Negated())
|
||||
index := relative.X + relative.Y*radarSpan
|
||||
objs[index] = byte(objects.Rover)
|
||||
}
|
||||
}
|
||||
|
||||
return radar, objs, nil
|
||||
}
|
||||
|
||||
// RoverCommands returns current commands for the given rover
|
||||
func (w *World) RoverCommands(rover string) (incoming []Command, queued []Command) {
|
||||
if c, ok := w.CommandIncoming[rover]; ok {
|
||||
incoming = c
|
||||
}
|
||||
if c, ok := w.CommandQueue[rover]; ok {
|
||||
queued = c
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Enqueue will queue the commands given
|
||||
func (w *World) Enqueue(rover string, commands ...Command) error {
|
||||
|
||||
// First validate the commands
|
||||
for _, c := range commands {
|
||||
switch c.Command {
|
||||
case roveapi.CommandType_move:
|
||||
if _, err := bearing.FromString(c.Bearing); err != nil {
|
||||
return fmt.Errorf("unknown bearing: %s", c.Bearing)
|
||||
}
|
||||
case roveapi.CommandType_broadcast:
|
||||
if len(c.Message) > 3 {
|
||||
return fmt.Errorf("too many characters in message (limit 3): %d", len(c.Message))
|
||||
}
|
||||
for _, b := range c.Message {
|
||||
if b < 37 || b > 126 {
|
||||
return fmt.Errorf("invalid message character: %c", b)
|
||||
}
|
||||
}
|
||||
case roveapi.CommandType_stash:
|
||||
case roveapi.CommandType_repair:
|
||||
case roveapi.CommandType_recharge:
|
||||
// Nothing to verify
|
||||
default:
|
||||
return fmt.Errorf("unknown command: %s", c.Command)
|
||||
}
|
||||
}
|
||||
|
||||
// Lock our commands edit
|
||||
w.cmdMutex.Lock()
|
||||
defer w.cmdMutex.Unlock()
|
||||
|
||||
// Override the incoming command set
|
||||
w.CommandIncoming[rover] = commands
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnqueueAllIncoming will enqueue the incoming commands
|
||||
func (w *World) EnqueueAllIncoming() {
|
||||
// Add any incoming commands from this tick and clear that queue
|
||||
for id, incoming := range w.CommandIncoming {
|
||||
commands := w.CommandQueue[id]
|
||||
commands = append(commands, incoming...)
|
||||
w.CommandQueue[id] = commands
|
||||
}
|
||||
w.CommandIncoming = make(map[string]CommandStream)
|
||||
}
|
||||
|
||||
// ExecuteCommandQueues will execute any commands in the current command queue
|
||||
func (w *World) ExecuteCommandQueues() {
|
||||
w.cmdMutex.Lock()
|
||||
defer w.cmdMutex.Unlock()
|
||||
|
||||
// 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 {
|
||||
log.Println(err)
|
||||
// TODO: Report this error somehow
|
||||
}
|
||||
|
||||
} else {
|
||||
// Clean out the empty entry
|
||||
delete(w.CommandQueue, rover)
|
||||
}
|
||||
}
|
||||
|
||||
// Add any incoming commands from this tick and clear that queue
|
||||
w.EnqueueAllIncoming()
|
||||
|
||||
// Increment the current tick count
|
||||
w.CurrentTicks++
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
switch c.Command {
|
||||
case roveapi.CommandType_move:
|
||||
if dir, err := bearing.FromString(c.Bearing); err != nil {
|
||||
return err
|
||||
} else if _, err := w.MoveRover(rover, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case roveapi.CommandType_stash:
|
||||
if _, err := w.RoverStash(rover); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case roveapi.CommandType_repair:
|
||||
r, err := w.GetRover(rover)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Consume an inventory item to repair if possible
|
||||
if len(r.Inventory) > 0 && r.Integrity < r.MaximumIntegrity {
|
||||
r.Inventory = r.Inventory[:len(r.Inventory)-1]
|
||||
r.Integrity = r.Integrity + 1
|
||||
r.AddLogEntryf("repaired self to %d", r.Integrity)
|
||||
w.Rovers[rover] = r
|
||||
}
|
||||
case roveapi.CommandType_recharge:
|
||||
_, err := w.RoverRecharge(rover)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case roveapi.CommandType_broadcast:
|
||||
if err := w.RoverBroadcast(rover, c.Message); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown command: %s", c.Command)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Daytime returns if it's currently daytime
|
||||
// for simplicity this uses the 1st half of the day as daytime, the 2nd half as nighttime
|
||||
func (w *World) Daytime() bool {
|
||||
tickInDay := w.CurrentTicks % w.TicksPerDay
|
||||
return tickInDay < w.TicksPerDay/2
|
||||
}
|
||||
|
||||
// RLock read locks the world
|
||||
func (w *World) RLock() {
|
||||
w.worldMutex.RLock()
|
||||
w.cmdMutex.RLock()
|
||||
}
|
||||
|
||||
// RUnlock read unlocks the world
|
||||
func (w *World) RUnlock() {
|
||||
w.worldMutex.RUnlock()
|
||||
w.cmdMutex.RUnlock()
|
||||
}
|
||||
|
||||
// Lock locks the world
|
||||
func (w *World) Lock() {
|
||||
w.worldMutex.Lock()
|
||||
w.cmdMutex.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks the world
|
||||
func (w *World) Unlock() {
|
||||
w.worldMutex.Unlock()
|
||||
w.cmdMutex.Unlock()
|
||||
}
|
430
pkg/rove/world_test.go
Normal file
430
pkg/rove/world_test.go
Normal file
|
@ -0,0 +1,430 @@
|
|||
package rove
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mdiluz/rove/pkg/atlas"
|
||||
"github.com/mdiluz/rove/pkg/bearing"
|
||||
"github.com/mdiluz/rove/pkg/objects"
|
||||
"github.com/mdiluz/rove/pkg/roveapi"
|
||||
"github.com/mdiluz/rove/pkg/vector"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewWorld(t *testing.T) {
|
||||
// Very basic for now, nothing to verify
|
||||
world := NewWorld(4)
|
||||
if world == nil {
|
||||
t.Error("Failed to create world")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorld_CreateRover(t *testing.T) {
|
||||
world := NewWorld(8)
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
b, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Basic duplicate check
|
||||
if a == b {
|
||||
t.Errorf("Created identical rovers")
|
||||
} else if len(world.Rovers) != 2 {
|
||||
t.Errorf("Incorrect number of rovers created")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorld_GetRover(t *testing.T) {
|
||||
world := NewWorld(4)
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
rover, err := world.GetRover(a)
|
||||
assert.NoError(t, err, "Failed to get rover attribs")
|
||||
assert.NotZero(t, rover.Range, "Rover should not be spawned blind")
|
||||
assert.Contains(t, rover.Logs[len(rover.Logs)-1].Text, "created", "Rover logs should contain the creation")
|
||||
}
|
||||
|
||||
func TestWorld_DestroyRover(t *testing.T) {
|
||||
world := NewWorld(1)
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
b, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = world.DestroyRover(a)
|
||||
assert.NoError(t, err, "Error returned from rover destroy")
|
||||
|
||||
// Basic duplicate check
|
||||
if len(world.Rovers) != 1 {
|
||||
t.Error("Too many rovers left in world")
|
||||
} else if _, ok := world.Rovers[b]; !ok {
|
||||
t.Error("Remaining rover is incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorld_GetSetMovePosition(t *testing.T) {
|
||||
world := NewWorld(4)
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
pos := vector.Vector{
|
||||
X: 0.0,
|
||||
Y: 0.0,
|
||||
}
|
||||
|
||||
err = world.WarpRover(a, pos)
|
||||
assert.NoError(t, err, "Failed to set position for rover")
|
||||
|
||||
newPos, err := world.RoverPosition(a)
|
||||
assert.NoError(t, err, "Failed to set position for rover")
|
||||
assert.Equal(t, pos, newPos, "Failed to correctly set position for rover")
|
||||
|
||||
b := bearing.North
|
||||
newPos, err = world.MoveRover(a, b)
|
||||
assert.NoError(t, err, "Failed to set position for rover")
|
||||
pos.Add(vector.Vector{X: 0, Y: 1})
|
||||
assert.Equal(t, pos, newPos, "Failed to correctly move position for rover")
|
||||
|
||||
rover, err := world.GetRover(a)
|
||||
assert.NoError(t, err, "Failed to get rover information")
|
||||
assert.Equal(t, rover.MaximumCharge-1, rover.Charge, "Rover should have lost charge for moving")
|
||||
assert.Contains(t, rover.Logs[len(rover.Logs)-1].Text, "moved", "Rover logs should contain the move")
|
||||
|
||||
// Place a tile in front of the rover
|
||||
world.Atlas.SetObject(vector.Vector{X: 0, Y: 2}, objects.Object{Type: objects.LargeRock})
|
||||
newPos, err = world.MoveRover(a, b)
|
||||
assert.NoError(t, err, "Failed to move rover")
|
||||
assert.Equal(t, pos, newPos, "Failed to correctly not move position for rover into wall")
|
||||
|
||||
rover, err = world.GetRover(a)
|
||||
assert.NoError(t, err, "Failed to get rover information")
|
||||
assert.Equal(t, rover.MaximumCharge-2, rover.Charge, "Rover should have lost charge for move attempt")
|
||||
}
|
||||
|
||||
func TestWorld_RadarFromRover(t *testing.T) {
|
||||
// Create world that should have visible walls on the radar
|
||||
world := NewWorld(2)
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
b, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Warp the rovers into position
|
||||
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.Vector{X: 0, Y: 0}), "Failed to warp rover")
|
||||
|
||||
radar, objs, err := world.RadarFromRover(a)
|
||||
assert.NoError(t, err, "Failed to get radar from rover")
|
||||
fullRange := 4 + 4 + 1
|
||||
assert.Equal(t, fullRange*fullRange, len(radar), "Radar returned wrong length")
|
||||
assert.Equal(t, fullRange*fullRange, len(objs), "Radar returned wrong length")
|
||||
|
||||
// Test the expected values
|
||||
assert.Equal(t, byte(objects.Rover), objs[1+fullRange])
|
||||
assert.Equal(t, byte(objects.Rover), objs[4+4*fullRange])
|
||||
|
||||
// Check the radar results are stable
|
||||
radar1, objs1, err := world.RadarFromRover(a)
|
||||
assert.NoError(t, err)
|
||||
radar2, objs2, err := world.RadarFromRover(a)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, radar1, radar2)
|
||||
assert.Equal(t, objs1, objs2)
|
||||
}
|
||||
|
||||
func TestWorld_RoverStash(t *testing.T) {
|
||||
world := NewWorld(2)
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
pos := vector.Vector{
|
||||
X: 0.0,
|
||||
Y: 0.0,
|
||||
}
|
||||
|
||||
err = world.WarpRover(a, pos)
|
||||
assert.NoError(t, err, "Failed to set position for rover")
|
||||
|
||||
// Set to a traversible tile
|
||||
world.Atlas.SetTile(pos, atlas.TileRock)
|
||||
|
||||
rover, err := world.GetRover(a)
|
||||
assert.NoError(t, err, "Failed to get rover")
|
||||
|
||||
for i := 0; i < rover.Capacity; i++ {
|
||||
// Place an object
|
||||
world.Atlas.SetObject(pos, objects.Object{Type: objects.SmallRock})
|
||||
|
||||
// Pick it up
|
||||
o, err := world.RoverStash(a)
|
||||
assert.NoError(t, err, "Failed to stash")
|
||||
assert.Equal(t, objects.SmallRock, o, "Failed to get correct object")
|
||||
|
||||
// Check it's gone
|
||||
_, obj := world.Atlas.QueryPosition(pos)
|
||||
assert.Equal(t, objects.None, obj.Type, "Stash failed to remove object from atlas")
|
||||
|
||||
// Check we have it
|
||||
inv, err := world.RoverInventory(a)
|
||||
assert.NoError(t, err, "Failed to get inventory")
|
||||
assert.Equal(t, i+1, len(inv))
|
||||
assert.Equal(t, objects.Object{Type: objects.SmallRock}, inv[i])
|
||||
|
||||
// Check that this did reduce the charge
|
||||
info, err := world.GetRover(a)
|
||||
assert.NoError(t, err, "Failed to get rover")
|
||||
assert.Equal(t, info.MaximumCharge-(i+1), info.Charge, "Rover lost charge for stash")
|
||||
assert.Contains(t, info.Logs[len(info.Logs)-1].Text, "stashed", "Rover logs should contain the move")
|
||||
}
|
||||
|
||||
// Recharge the rover
|
||||
for i := 0; i < rover.MaximumCharge; i++ {
|
||||
_, err = world.RoverRecharge(a)
|
||||
assert.NoError(t, err)
|
||||
|
||||
}
|
||||
|
||||
// Place an object
|
||||
world.Atlas.SetObject(pos, objects.Object{Type: objects.SmallRock})
|
||||
|
||||
// Try to pick it up
|
||||
o, err := world.RoverStash(a)
|
||||
assert.NoError(t, err, "Failed to stash")
|
||||
assert.Equal(t, objects.None, o, "Failed to get correct object")
|
||||
|
||||
// Check it's still there
|
||||
_, obj := world.Atlas.QueryPosition(pos)
|
||||
assert.Equal(t, objects.SmallRock, obj.Type, "Stash failed to remove object from atlas")
|
||||
|
||||
// Check we don't have it
|
||||
inv, err := world.RoverInventory(a)
|
||||
assert.NoError(t, err, "Failed to get inventory")
|
||||
assert.Equal(t, rover.Capacity, len(inv))
|
||||
|
||||
// Check that this didn't reduce the charge
|
||||
info, err := world.GetRover(a)
|
||||
assert.NoError(t, err, "Failed to get rover")
|
||||
assert.Equal(t, info.MaximumCharge, info.Charge, "Rover lost charge for non-stash")
|
||||
}
|
||||
|
||||
func TestWorld_RoverDamage(t *testing.T) {
|
||||
world := NewWorld(2)
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
pos := vector.Vector{
|
||||
X: 0.0,
|
||||
Y: 0.0,
|
||||
}
|
||||
|
||||
err = world.WarpRover(a, pos)
|
||||
assert.NoError(t, err, "Failed to set position for rover")
|
||||
|
||||
info, err := world.GetRover(a)
|
||||
assert.NoError(t, err, "couldn't get rover info")
|
||||
|
||||
world.Atlas.SetObject(vector.Vector{X: 0.0, Y: 1.0}, objects.Object{Type: objects.LargeRock})
|
||||
|
||||
vec, err := world.MoveRover(a, bearing.North)
|
||||
assert.NoError(t, err, "Failed to move rover")
|
||||
assert.Equal(t, pos, vec, "Rover managed to move into large rock")
|
||||
|
||||
newinfo, err := world.GetRover(a)
|
||||
assert.NoError(t, err, "couldn't get rover info")
|
||||
assert.Equal(t, info.Integrity-1, newinfo.Integrity, "rover should have lost integrity")
|
||||
assert.Contains(t, newinfo.Logs[len(newinfo.Logs)-1].Text, "collision", "Rover logs should contain the collision")
|
||||
}
|
||||
|
||||
func TestWorld_RoverRepair(t *testing.T) {
|
||||
world := NewWorld(2)
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
pos := vector.Vector{
|
||||
X: 0.0,
|
||||
Y: 0.0,
|
||||
}
|
||||
|
||||
world.Atlas.SetTile(pos, atlas.TileNone)
|
||||
world.Atlas.SetObject(pos, objects.Object{Type: objects.None})
|
||||
|
||||
err = world.WarpRover(a, pos)
|
||||
assert.NoError(t, err, "Failed to set position for rover")
|
||||
|
||||
originalInfo, err := world.GetRover(a)
|
||||
assert.NoError(t, err, "couldn't get rover info")
|
||||
|
||||
// Pick up something to repair with
|
||||
world.Atlas.SetObject(pos, objects.Object{Type: objects.SmallRock})
|
||||
o, err := world.RoverStash(a)
|
||||
assert.NoError(t, err, "Failed to stash")
|
||||
assert.Equal(t, objects.SmallRock, o, "Failed to get correct object")
|
||||
|
||||
world.Atlas.SetObject(vector.Vector{X: 0.0, Y: 1.0}, objects.Object{Type: objects.LargeRock})
|
||||
|
||||
// Try and bump into the rock
|
||||
vec, err := world.MoveRover(a, bearing.North)
|
||||
assert.NoError(t, err, "Failed to move rover")
|
||||
assert.Equal(t, pos, vec, "Rover managed to move into large rock")
|
||||
|
||||
newinfo, err := world.GetRover(a)
|
||||
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)
|
||||
assert.NoError(t, err, "Failed to repair rover")
|
||||
|
||||
newinfo, err = world.GetRover(a)
|
||||
assert.NoError(t, err, "couldn't get rover info")
|
||||
assert.Equal(t, originalInfo.Integrity, newinfo.Integrity, "rover should have gained integrity")
|
||||
assert.Contains(t, newinfo.Logs[len(newinfo.Logs)-1].Text, "repair", "Rover logs should contain the repair")
|
||||
|
||||
// Check again that it can't repair past the max
|
||||
world.Atlas.SetObject(pos, objects.Object{Type: objects.SmallRock})
|
||||
o, err = world.RoverStash(a)
|
||||
assert.NoError(t, err, "Failed to stash")
|
||||
assert.Equal(t, objects.SmallRock, o, "Failed to get correct object")
|
||||
|
||||
err = world.ExecuteCommand(&Command{Command: roveapi.CommandType_repair}, a)
|
||||
assert.NoError(t, err, "Failed to repair rover")
|
||||
|
||||
newinfo, err = world.GetRover(a)
|
||||
assert.NoError(t, err, "couldn't get rover info")
|
||||
assert.Equal(t, originalInfo.Integrity, newinfo.Integrity, "rover should have kept the same integrity")
|
||||
}
|
||||
|
||||
func TestWorld_Charge(t *testing.T) {
|
||||
world := NewWorld(4)
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get the rover information
|
||||
rover, err := world.GetRover(a)
|
||||
assert.NoError(t, err, "Failed to get rover information")
|
||||
assert.Equal(t, rover.MaximumCharge, rover.Charge, "Rover should start with maximum charge")
|
||||
|
||||
// Use up all the charge
|
||||
for i := 0; i < rover.MaximumCharge; i++ {
|
||||
// Get the initial position
|
||||
initialPos, err := world.RoverPosition(a)
|
||||
assert.NoError(t, err, "Failed to get position for rover")
|
||||
|
||||
// Ensure the path ahead is empty
|
||||
world.Atlas.SetTile(initialPos.Added(bearing.North.Vector()), atlas.TileRock)
|
||||
world.Atlas.SetObject(initialPos.Added(bearing.North.Vector()), objects.Object{Type: objects.None})
|
||||
|
||||
// Try and move north (along unblocked path)
|
||||
newPos, err := world.MoveRover(a, bearing.North)
|
||||
assert.NoError(t, err, "Failed to set position for rover")
|
||||
assert.Equal(t, initialPos.Added(bearing.North.Vector()), newPos, "Failed to correctly move position for rover")
|
||||
|
||||
// Ensure rover lost charge
|
||||
rover, err := world.GetRover(a)
|
||||
assert.NoError(t, err, "Failed to get rover information")
|
||||
assert.Equal(t, rover.MaximumCharge-(i+1), rover.Charge, "Rover should have lost charge")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestWorld_Daytime(t *testing.T) {
|
||||
world := NewWorld(1)
|
||||
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Remove rover charge
|
||||
rover := world.Rovers[a]
|
||||
rover.Charge = 0
|
||||
world.Rovers[a] = rover
|
||||
|
||||
// Try and recharge, should work
|
||||
_, err = world.RoverRecharge(a)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, world.Rovers[a].Charge)
|
||||
|
||||
// Loop for half the day
|
||||
for i := 0; i < world.TicksPerDay/2; i++ {
|
||||
assert.True(t, world.Daytime())
|
||||
world.ExecuteCommandQueues()
|
||||
}
|
||||
|
||||
// Remove rover charge again
|
||||
rover = world.Rovers[a]
|
||||
rover.Charge = 0
|
||||
world.Rovers[a] = rover
|
||||
|
||||
// Try and recharge, should fail
|
||||
_, err = world.RoverRecharge(a)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, world.Rovers[a].Charge)
|
||||
|
||||
// Loop for half the day
|
||||
for i := 0; i < world.TicksPerDay/2; i++ {
|
||||
assert.False(t, world.Daytime())
|
||||
world.ExecuteCommandQueues()
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorld_Broadcast(t *testing.T) {
|
||||
world := NewWorld(8)
|
||||
|
||||
a, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
b, err := world.SpawnRover()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Warp rovers near to eachother
|
||||
assert.NoError(t, world.WarpRover(a, vector.Vector{X: 0, Y: 0}))
|
||||
assert.NoError(t, world.WarpRover(b, vector.Vector{X: 1, Y: 0}))
|
||||
|
||||
// Broadcast from a
|
||||
assert.NoError(t, world.RoverBroadcast(a, []byte{'A', 'B', 'C'}))
|
||||
|
||||
// Check if b heard it
|
||||
ra, err := world.GetRover(a)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ra.MaximumCharge-1, ra.Charge, "A should have used a charge to broadcast")
|
||||
assert.Contains(t, ra.Logs[len(ra.Logs)-1].Text, "ABC", "Rover B should have heard the broadcast")
|
||||
|
||||
// Check if a logged it
|
||||
rb, err := world.GetRover(b)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rb.Logs[len(rb.Logs)-1].Text, "ABC", "Rover A should have logged it's broadcast")
|
||||
|
||||
// Warp B outside of the range of A
|
||||
world.Atlas.SetObject(vector.Vector{X: ra.Range, Y: 0}, objects.Object{Type: objects.None})
|
||||
assert.NoError(t, world.WarpRover(b, vector.Vector{X: ra.Range, Y: 0}))
|
||||
|
||||
// Broadcast from a again
|
||||
assert.NoError(t, world.RoverBroadcast(a, []byte{'X', 'Y', 'Z'}))
|
||||
|
||||
// Check if b heard it
|
||||
ra, err = world.GetRover(b)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, ra.Logs[len(ra.Logs)-1].Text, "XYZ", "Rover B should not have heard the broadcast")
|
||||
|
||||
// Check if a logged it
|
||||
rb, err = world.GetRover(a)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rb.Logs[len(rb.Logs)-1].Text, "XYZ", "Rover A should have logged it's broadcast")
|
||||
|
||||
// Warp B outside of the range of A
|
||||
world.Atlas.SetObject(vector.Vector{X: ra.Range + 1, Y: 0}, objects.Object{Type: objects.None})
|
||||
assert.NoError(t, world.WarpRover(b, vector.Vector{X: ra.Range + 1, Y: 0}))
|
||||
|
||||
// Broadcast from a again
|
||||
assert.NoError(t, world.RoverBroadcast(a, []byte{'H', 'J', 'K'}))
|
||||
|
||||
// Check if b heard it
|
||||
ra, err = world.GetRover(b)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, ra.Logs[len(ra.Logs)-1].Text, "HJK", "Rover B should have heard the broadcast")
|
||||
|
||||
// Check if a logged it
|
||||
rb, err = world.GetRover(a)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rb.Logs[len(rb.Logs)-1].Text, "HJK", "Rover A should have logged it's broadcast")
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue