Implement current wind direction and rover wind movement

This commit is contained in:
Marc Di Luzio 2020-07-22 23:36:13 +01:00
parent c94ac68f44
commit c89c5f6e74
7 changed files with 174 additions and 119 deletions

View file

@ -107,3 +107,13 @@ func BearingToVector(b roveapi.Bearing) Vector {
return Vector{}
}
// Dot returns the dot product of two vectors
func Dot(a Vector, b Vector) int {
return a.X*b.X + a.Y*b.Y
}
// AngleCos returns the cosine of the angle between two vectors
func AngleCos(a Vector, b Vector) float64 {
return float64(Dot(a, b)) / a.Length() * b.Length()
}

View file

@ -57,6 +57,9 @@ type Rover struct {
// SailPosition is the current position of the sails
SailPosition roveapi.SailPosition
// Current number of ticks in this move, used for sailing speeds
MoveTicks int
// Logs Stores log of information
Logs []RoverLogEntry
}

View file

@ -10,11 +10,16 @@ import (
"github.com/mdiluz/rove/proto/roveapi"
)
const (
TicksPerNormalMove = 4
)
// 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
TicksPerDay int
@ -27,6 +32,9 @@ type World struct {
// Atlas represends the world map of chunks and tiles
Atlas Atlas
// Wind is the current wind direction
Wind roveapi.Bearing
// Commands is the set of currently executing command streams per rover
CommandQueue map[string]CommandStream
// Incoming represents the set of commands to add to the queue at the end of the current tick
@ -230,8 +238,8 @@ func (w *World) WarpRover(rover string, pos maths.Vector) error {
return nil
}
// MoveRover attempts to move a rover in a specific direction
func (w *World) MoveRover(rover string, b roveapi.Bearing) (maths.Vector, error) {
// TryMoveRover attempts to move a rover in a specific direction
func (w *World) TryMoveRover(rover string, b roveapi.Bearing) (maths.Vector, error) {
w.worldMutex.Lock()
defer w.worldMutex.Unlock()
@ -240,12 +248,6 @@ func (w *World) MoveRover(rover string, b roveapi.Bearing) (maths.Vector, error)
return maths.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(maths.BearingToVector(b))
@ -318,7 +320,9 @@ func (w *World) RoverToggle(rover string) (roveapi.SailPosition, error) {
r.SailPosition = roveapi.SailPosition_CatchingWind
}
w.Rovers[rover] = r
// Reset the movement ticks
r.MoveTicks = 0
return r.SailPosition, nil
}
@ -334,6 +338,8 @@ func (w *World) RoverTurn(rover string, bearing roveapi.Bearing) (roveapi.Bearin
// Set the new bearing
r.Bearing = bearing
// Reset the movement ticks
r.MoveTicks = 0
return r.Bearing, nil
}
@ -502,6 +508,64 @@ func (w *World) Tick() {
// Add any incoming commands from this tick and clear that queue
w.EnqueueAllIncoming()
// Change the wind every day
if (w.CurrentTicks % w.TicksPerDay) == 0 {
w.Wind = roveapi.Bearing((rand.Int() % 8) + 1) // Random cardinal bearing
}
// Move all the rovers based on current wind and sails
for n, r := range w.Rovers {
// Skip if we're not catching the wind
if r.SailPosition != roveapi.SailPosition_CatchingWind {
continue
}
// Increment the current move ticks
r.MoveTicks++
// Get the difference between the two bearings
// Normalise, we don't care about clockwise/anticlockwise
diff := maths.Abs(int(w.Wind - r.Bearing))
if diff > 4 {
diff = 8 - diff
}
// Calculate the travel "ticks"
var ticksToMove int
switch diff {
case 0:
// Going with the wind, travel at base speed of once every 4 ticks
ticksToMove = TicksPerNormalMove
case 1:
// At a slight angle, we can go a little faster
ticksToMove = TicksPerNormalMove / 2
case 2:
// Perpendicular to wind, max speed
ticksToMove = 1
case 3:
// Heading at 45 degrees into the wind, back to min speed
ticksToMove = TicksPerNormalMove
case 4:
// Heading durectly into the wind, no movement at all
default:
log.Fatalf("bearing difference of %d should be impossible", diff)
}
// If we've incremented over the current move ticks on the rover, we can try and make the move
if r.MoveTicks >= ticksToMove {
_, err := w.TryMoveRover(n, r.Bearing)
if err != nil {
log.Println(err)
// TODO: Report this error somehow
}
// Reset the move ticks
r.MoveTicks = 0
}
log.Print(ticksToMove)
}
// Increment the current tick count
w.CurrentTicks++
}

View file

@ -78,25 +78,20 @@ func TestWorld_GetSetMovePosition(t *testing.T) {
assert.Equal(t, pos, newPos, "Failed to correctly set position for rover")
b := roveapi.Bearing_North
newPos, err = world.MoveRover(a, b)
newPos, err = world.TryMoveRover(a, b)
assert.NoError(t, err, "Failed to set position for rover")
pos.Add(maths.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(maths.Vector{X: 0, Y: 2}, Object{Type: roveapi.Object_RockLarge})
newPos, err = world.MoveRover(a, b)
newPos, err = world.TryMoveRover(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) {
@ -224,7 +219,7 @@ func TestWorld_RoverDamage(t *testing.T) {
world.Atlas.SetObject(maths.Vector{X: 0.0, Y: 1.0}, Object{Type: roveapi.Object_RockLarge})
vec, err := world.MoveRover(a, roveapi.Bearing_North)
vec, err := world.TryMoveRover(a, roveapi.Bearing_North)
assert.NoError(t, err, "Failed to move rover")
assert.Equal(t, pos, vec, "Rover managed to move into large rock")
@ -261,7 +256,7 @@ func TestWorld_RoverRepair(t *testing.T) {
world.Atlas.SetObject(maths.Vector{X: 0.0, Y: 1.0}, Object{Type: roveapi.Object_RockLarge})
// Try and bump into the rock
vec, err := world.MoveRover(a, roveapi.Bearing_North)
vec, err := world.TryMoveRover(a, roveapi.Bearing_North)
assert.NoError(t, err, "Failed to move rover")
assert.Equal(t, pos, vec, "Rover managed to move into large rock")
@ -291,39 +286,6 @@ func TestWorld_RoverRepair(t *testing.T) {
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(maths.BearingToVector(roveapi.Bearing_North)), roveapi.Tile_Rock)
world.Atlas.SetObject(initialPos.Added(maths.BearingToVector(roveapi.Bearing_North)), Object{Type: roveapi.Object_ObjectUnknown})
// Try and move north (along unblocked path)
newPos, err := world.MoveRover(a, roveapi.Bearing_North)
assert.NoError(t, err, "Failed to set 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)
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)