Merge pull request #28 from mdiluz/dormant-rovers

Dormant rovers
This commit is contained in:
Marc Di Luzio 2020-07-19 19:20:03 +01:00 committed by GitHub
commit 89123394cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 246 additions and 153 deletions

View file

@ -22,6 +22,9 @@ const (
// GlyphRoverLive represents a live rover // GlyphRoverLive represents a live rover
GlyphRoverLive = Glyph('R') GlyphRoverLive = Glyph('R')
// GlyphRoverDormant represents a dormant rover
GlyphRoverDormant = Glyph('r')
// GlyphRockSmall is a small stashable rock // GlyphRockSmall is a small stashable rock
GlyphRockSmall = Glyph('o') GlyphRockSmall = Glyph('o')
@ -51,6 +54,8 @@ func ObjectGlyph(o roveapi.Object) Glyph {
return GlyphRoverLive return GlyphRoverLive
case roveapi.Object_RockSmall: case roveapi.Object_RockSmall:
return GlyphRockSmall return GlyphRockSmall
case roveapi.Object_RoverDormant:
return GlyphRoverDormant
case roveapi.Object_RockLarge: case roveapi.Object_RockLarge:
return GlyphRockLarge return GlyphRockLarge
} }

View file

@ -1,4 +1,4 @@
package atlas package rove
import ( import (
"github.com/mdiluz/rove/pkg/maths" "github.com/mdiluz/rove/pkg/maths"

View file

@ -1,4 +1,4 @@
package atlas package rove
import ( import (
"testing" "testing"

View file

@ -1,4 +1,4 @@
package atlas package rove
import ( import (
"log" "log"
@ -6,7 +6,6 @@ import (
"github.com/mdiluz/rove/pkg/maths" "github.com/mdiluz/rove/pkg/maths"
"github.com/mdiluz/rove/proto/roveapi" "github.com/mdiluz/rove/proto/roveapi"
"github.com/ojrac/opensimplex-go"
) )
// chunk represents a fixed square grid of tiles // chunk represents a fixed square grid of tiles
@ -34,29 +33,23 @@ type chunkBasedAtlas struct {
// ChunkSize is the x/y dimensions of each square chunk // ChunkSize is the x/y dimensions of each square chunk
ChunkSize int `json:"chunksize"` ChunkSize int `json:"chunksize"`
// terrainNoise describes the noise function for the terrain // worldGen is the internal world generator
terrainNoise opensimplex.Noise worldGen WorldGen
// terrainNoise describes the noise function for the terrain
objectNoise opensimplex.Noise
} }
const ( const (
noiseSeed = 1024 noiseSeed = 1024
terrainNoiseScale = 6
objectNoiseScale = 3
) )
// NewChunkAtlas creates a new empty atlas // NewChunkAtlas creates a new empty atlas
func NewChunkAtlas(chunkSize int) Atlas { func NewChunkAtlas(chunkSize int) Atlas {
// Start up with one chunk // Start up with one chunk
a := chunkBasedAtlas{ a := chunkBasedAtlas{
ChunkSize: chunkSize, ChunkSize: chunkSize,
Chunks: make([]chunk, 1), Chunks: make([]chunk, 1),
LowerBound: maths.Vector{X: 0, Y: 0}, LowerBound: maths.Vector{X: 0, Y: 0},
UpperBound: maths.Vector{X: chunkSize, Y: chunkSize}, UpperBound: maths.Vector{X: chunkSize, Y: chunkSize},
terrainNoise: opensimplex.New(noiseSeed), worldGen: NewNoiseWorldGen(noiseSeed),
objectNoise: opensimplex.New(noiseSeed),
} }
// Initialise the first chunk // Initialise the first chunk
a.populate(0) a.populate(0)
@ -105,31 +98,15 @@ func (a *chunkBasedAtlas) populate(chunk int) {
origin := a.chunkOriginInWorldSpace(chunk) origin := a.chunkOriginInWorldSpace(chunk)
for i := 0; i < a.ChunkSize; i++ { for i := 0; i < a.ChunkSize; i++ {
for j := 0; j < a.ChunkSize; j++ { for j := 0; j < a.ChunkSize; j++ {
loc := maths.Vector{X: origin.X + i, Y: origin.Y + j}
// Get the terrain noise value for this location // Set the tile
t := a.terrainNoise.Eval2(float64(origin.X+i)/terrainNoiseScale, float64(origin.Y+j)/terrainNoiseScale) c.Tiles[j*a.ChunkSize+i] = byte(a.worldGen.GetTile(loc))
var tile roveapi.Tile
switch {
case t > 0.5:
tile = roveapi.Tile_Gravel
case t > 0.05:
tile = roveapi.Tile_Sand
default:
tile = roveapi.Tile_Rock
}
c.Tiles[j*a.ChunkSize+i] = byte(tile)
// Get the object noise value for this location // Set the object
o := a.objectNoise.Eval2(float64(origin.X+i)/objectNoiseScale, float64(origin.Y+j)/objectNoiseScale) obj := a.worldGen.GetObject(loc)
var obj = roveapi.Object_ObjectUnknown if obj.Type != roveapi.Object_ObjectUnknown {
switch { c.Objects[j*a.ChunkSize+i] = obj
case o > 0.6:
obj = roveapi.Object_RockLarge
case o > 0.5:
obj = roveapi.Object_RockSmall
}
if obj != roveapi.Object_ObjectUnknown {
c.Objects[j*a.ChunkSize+i] = Object{Type: roveapi.Object(obj)}
} }
} }
} }
@ -236,12 +213,11 @@ func (a *chunkBasedAtlas) worldSpaceToChunkWithGrow(v maths.Vector) int {
// Create the new empty atlas // Create the new empty atlas
newAtlas := chunkBasedAtlas{ newAtlas := chunkBasedAtlas{
ChunkSize: a.ChunkSize, ChunkSize: a.ChunkSize,
LowerBound: lower, LowerBound: lower,
UpperBound: upper, UpperBound: upper,
Chunks: make([]chunk, size.X*size.Y), Chunks: make([]chunk, size.X*size.Y),
terrainNoise: a.terrainNoise, worldGen: a.worldGen,
objectNoise: a.objectNoise,
} }
// Log that we're resizing // Log that we're resizing

View file

@ -1,4 +1,4 @@
package atlas package rove
import ( import (
"github.com/mdiluz/rove/proto/roveapi" "github.com/mdiluz/rove/proto/roveapi"
@ -8,12 +8,16 @@ import (
type Object struct { type Object struct {
// The type of the object // The type of the object
Type roveapi.Object `json:"type"` Type roveapi.Object `json:"type"`
// Data is an internal type used for certain types of object
Data []byte `json:"data"`
} }
// IsBlocking checks if an object is a blocking object // IsBlocking checks if an object is a blocking object
func (o *Object) IsBlocking() bool { func (o *Object) IsBlocking() bool {
var blocking = [...]roveapi.Object{ var blocking = [...]roveapi.Object{
roveapi.Object_RoverLive, roveapi.Object_RoverLive,
roveapi.Object_RoverDormant,
roveapi.Object_RockLarge, roveapi.Object_RockLarge,
} }

View file

@ -1,11 +1,14 @@
package rove package rove
import ( import (
"bufio"
"fmt" "fmt"
"log" "log"
"math/rand"
"os"
"time" "time"
"github.com/mdiluz/rove/pkg/atlas" "github.com/google/uuid"
"github.com/mdiluz/rove/pkg/maths" "github.com/mdiluz/rove/pkg/maths"
) )
@ -30,7 +33,7 @@ type Rover struct {
Range int `json:"range"` Range int `json:"range"`
// Inventory represents any items the rover is carrying // Inventory represents any items the rover is carrying
Inventory []atlas.Object `json:"inventory"` Inventory []Object `json:"inventory"`
// Capacity is the maximum number of inventory items // Capacity is the maximum number of inventory items
Capacity int `json:"capacity"` Capacity int `json:"capacity"`
@ -51,6 +54,19 @@ type Rover struct {
Logs []RoverLogEntry `json:"logs"` Logs []RoverLogEntry `json:"logs"`
} }
// DefaultRover returns a default rover object with default settings
func DefaultRover() Rover {
return Rover{
Range: 4,
Integrity: 10,
MaximumIntegrity: 10,
Capacity: 10,
Charge: 10,
MaximumCharge: 10,
Name: GenerateRoverName(),
}
}
// AddLogEntryf adds an entry to the rovers log // AddLogEntryf adds an entry to the rovers log
func (r *Rover) AddLogEntryf(format string, args ...interface{}) { func (r *Rover) AddLogEntryf(format string, args ...interface{}) {
text := fmt.Sprintf(format, args...) text := fmt.Sprintf(format, args...)
@ -62,3 +78,36 @@ func (r *Rover) AddLogEntryf(format string, args ...interface{}) {
}, },
) )
} }
var wordsFile = os.Getenv("WORDS_FILE")
var roverWords []string
// GenerateRoverName generates a new rover name
func GenerateRoverName() string {
// Try and load the rover words file
if len(roverWords) == 0 {
// Try and load the words file
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() {
roverWords = append(roverWords, scanner.Text())
}
if scanner.Err() != nil {
log.Printf("Failure during word file scan: %s\n", scanner.Err())
}
}
}
// Assign a random name if we have words
if len(roverWords) > 0 {
// Loop until we find a unique name
return fmt.Sprintf("%s-%s", roverWords[rand.Intn(len(roverWords))], roverWords[rand.Intn(len(roverWords))])
}
// Default to a unique string
return uuid.New().String()
}

View file

@ -1,15 +1,11 @@
package rove package rove
import ( import (
"bufio"
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
"os"
"sync" "sync"
"github.com/google/uuid"
"github.com/mdiluz/rove/pkg/atlas"
"github.com/mdiluz/rove/pkg/maths" "github.com/mdiluz/rove/pkg/maths"
"github.com/mdiluz/rove/proto/roveapi" "github.com/mdiluz/rove/proto/roveapi"
) )
@ -26,7 +22,7 @@ type World struct {
Rovers map[string]Rover `json:"rovers"` Rovers map[string]Rover `json:"rovers"`
// Atlas represends the world map of chunks and tiles // Atlas represends the world map of chunks and tiles
Atlas atlas.Atlas `json:"atlas"` Atlas Atlas `json:"atlas"`
// Commands is the set of currently executing command streams per rover // Commands is the set of currently executing command streams per rover
CommandQueue map[string]CommandStream `json:"commands"` CommandQueue map[string]CommandStream `json:"commands"`
@ -37,36 +33,15 @@ type World struct {
worldMutex sync.RWMutex worldMutex sync.RWMutex
// Mutex to lock around command operations // Mutex to lock around command operations
cmdMutex sync.RWMutex cmdMutex sync.RWMutex
// Set of possible words to use for names
words []string
} }
var wordsFile = os.Getenv("WORDS_FILE")
// NewWorld creates a new world object // NewWorld creates a new world object
func NewWorld(chunkSize int) *World { 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{ return &World{
Rovers: make(map[string]Rover), Rovers: make(map[string]Rover),
CommandQueue: make(map[string]CommandStream), CommandQueue: make(map[string]CommandStream),
CommandIncoming: make(map[string]CommandStream), CommandIncoming: make(map[string]CommandStream),
Atlas: atlas.NewChunkAtlas(chunkSize), Atlas: NewChunkAtlas(chunkSize),
words: lines,
TicksPerDay: 24, TicksPerDay: 24,
CurrentTicks: 0, CurrentTicks: 0,
} }
@ -78,27 +53,7 @@ func (w *World) SpawnRover() (string, error) {
defer w.worldMutex.Unlock() defer w.worldMutex.Unlock()
// Initialise the rover // Initialise the rover
rover := Rover{ rover := DefaultRover()
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 // Spawn in a random place near the origin
rover.Pos = maths.Vector{ rover.Pos = maths.Vector{
@ -240,7 +195,7 @@ func (w *World) SetRoverPosition(rover string, pos maths.Vector) error {
} }
// RoverInventory returns the inventory of a requested rover // RoverInventory returns the inventory of a requested rover
func (w *World) RoverInventory(rover string) ([]atlas.Object, error) { func (w *World) RoverInventory(rover string) ([]Object, error) {
w.worldMutex.RLock() w.worldMutex.RLock()
defer w.worldMutex.RUnlock() defer w.worldMutex.RUnlock()
@ -346,7 +301,7 @@ func (w *World) RoverStash(rover string) (roveapi.Object, error) {
r.AddLogEntryf("stashed %c", obj.Type) r.AddLogEntryf("stashed %c", obj.Type)
r.Inventory = append(r.Inventory, obj) r.Inventory = append(r.Inventory, obj)
w.Rovers[rover] = r w.Rovers[rover] = r
w.Atlas.SetObject(r.Pos, atlas.Object{Type: roveapi.Object_ObjectUnknown}) w.Atlas.SetObject(r.Pos, Object{Type: roveapi.Object_ObjectUnknown})
return obj.Type, nil return obj.Type, nil
} }

View file

@ -3,7 +3,6 @@ package rove
import ( import (
"testing" "testing"
"github.com/mdiluz/rove/pkg/atlas"
"github.com/mdiluz/rove/pkg/maths" "github.com/mdiluz/rove/pkg/maths"
"github.com/mdiluz/rove/proto/roveapi" "github.com/mdiluz/rove/proto/roveapi"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -90,7 +89,7 @@ func TestWorld_GetSetMovePosition(t *testing.T) {
assert.Contains(t, rover.Logs[len(rover.Logs)-1].Text, "moved", "Rover logs should contain the move") 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 // Place a tile in front of the rover
world.Atlas.SetObject(maths.Vector{X: 0, Y: 2}, atlas.Object{Type: roveapi.Object_RockLarge}) world.Atlas.SetObject(maths.Vector{X: 0, Y: 2}, Object{Type: roveapi.Object_RockLarge})
newPos, err = world.MoveRover(a, b) newPos, err = world.MoveRover(a, b)
assert.NoError(t, err, "Failed to move rover") assert.NoError(t, err, "Failed to move rover")
assert.Equal(t, pos, newPos, "Failed to correctly not move position for rover into wall") assert.Equal(t, pos, newPos, "Failed to correctly not move position for rover into wall")
@ -110,7 +109,9 @@ func TestWorld_RadarFromRover(t *testing.T) {
// Warp the rovers into position // Warp the rovers into position
bpos := maths.Vector{X: -3, Y: -3} bpos := maths.Vector{X: -3, Y: -3}
world.Atlas.SetObject(bpos, Object{Type: roveapi.Object_ObjectUnknown})
assert.NoError(t, world.WarpRover(b, bpos), "Failed to warp rover") assert.NoError(t, world.WarpRover(b, bpos), "Failed to warp rover")
world.Atlas.SetObject(maths.Vector{X: 0, Y: 0}, Object{Type: roveapi.Object_ObjectUnknown})
assert.NoError(t, world.WarpRover(a, maths.Vector{X: 0, Y: 0}), "Failed to warp rover") assert.NoError(t, world.WarpRover(a, maths.Vector{X: 0, Y: 0}), "Failed to warp rover")
radar, objs, err := world.RadarFromRover(a) radar, objs, err := world.RadarFromRover(a)
@ -142,7 +143,7 @@ func TestWorld_RoverStash(t *testing.T) {
Y: 0.0, Y: 0.0,
} }
world.Atlas.SetObject(pos, atlas.Object{Type: roveapi.Object_ObjectUnknown}) world.Atlas.SetObject(pos, Object{Type: roveapi.Object_ObjectUnknown})
err = world.WarpRover(a, pos) err = world.WarpRover(a, pos)
assert.NoError(t, err, "Failed to set position for rover") assert.NoError(t, err, "Failed to set position for rover")
@ -151,7 +152,7 @@ func TestWorld_RoverStash(t *testing.T) {
for i := 0; i < rover.Capacity; i++ { for i := 0; i < rover.Capacity; i++ {
// Place an object // Place an object
world.Atlas.SetObject(pos, atlas.Object{Type: roveapi.Object_RockSmall}) world.Atlas.SetObject(pos, Object{Type: roveapi.Object_RockSmall})
// Pick it up // Pick it up
o, err := world.RoverStash(a) o, err := world.RoverStash(a)
@ -166,7 +167,7 @@ func TestWorld_RoverStash(t *testing.T) {
inv, err := world.RoverInventory(a) inv, err := world.RoverInventory(a)
assert.NoError(t, err, "Failed to get inventory") assert.NoError(t, err, "Failed to get inventory")
assert.Equal(t, i+1, len(inv)) assert.Equal(t, i+1, len(inv))
assert.Equal(t, atlas.Object{Type: roveapi.Object_RockSmall}, inv[i]) assert.Equal(t, Object{Type: roveapi.Object_RockSmall}, inv[i])
// Check that this did reduce the charge // Check that this did reduce the charge
info, err := world.GetRover(a) info, err := world.GetRover(a)
@ -183,7 +184,7 @@ func TestWorld_RoverStash(t *testing.T) {
} }
// Place an object // Place an object
world.Atlas.SetObject(pos, atlas.Object{Type: roveapi.Object_RockSmall}) world.Atlas.SetObject(pos, Object{Type: roveapi.Object_RockSmall})
// Try to pick it up // Try to pick it up
o, err := world.RoverStash(a) o, err := world.RoverStash(a)
@ -221,7 +222,7 @@ func TestWorld_RoverDamage(t *testing.T) {
info, err := world.GetRover(a) info, err := world.GetRover(a)
assert.NoError(t, err, "couldn't get rover info") assert.NoError(t, err, "couldn't get rover info")
world.Atlas.SetObject(maths.Vector{X: 0.0, Y: 1.0}, atlas.Object{Type: roveapi.Object_RockLarge}) 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.MoveRover(a, roveapi.Bearing_North)
assert.NoError(t, err, "Failed to move rover") assert.NoError(t, err, "Failed to move rover")
@ -243,7 +244,7 @@ func TestWorld_RoverRepair(t *testing.T) {
Y: 0.0, Y: 0.0,
} }
world.Atlas.SetObject(pos, atlas.Object{Type: roveapi.Object_ObjectUnknown}) world.Atlas.SetObject(pos, Object{Type: roveapi.Object_ObjectUnknown})
err = world.WarpRover(a, pos) err = world.WarpRover(a, pos)
assert.NoError(t, err, "Failed to set position for rover") assert.NoError(t, err, "Failed to set position for rover")
@ -252,12 +253,12 @@ func TestWorld_RoverRepair(t *testing.T) {
assert.NoError(t, err, "couldn't get rover info") assert.NoError(t, err, "couldn't get rover info")
// Pick up something to repair with // Pick up something to repair with
world.Atlas.SetObject(pos, atlas.Object{Type: roveapi.Object_RockSmall}) world.Atlas.SetObject(pos, Object{Type: roveapi.Object_RockSmall})
o, err := world.RoverStash(a) o, err := world.RoverStash(a)
assert.NoError(t, err, "Failed to stash") assert.NoError(t, err, "Failed to stash")
assert.Equal(t, roveapi.Object_RockSmall, o, "Failed to get correct object") assert.Equal(t, roveapi.Object_RockSmall, o, "Failed to get correct object")
world.Atlas.SetObject(maths.Vector{X: 0.0, Y: 1.0}, atlas.Object{Type: roveapi.Object_RockLarge}) world.Atlas.SetObject(maths.Vector{X: 0.0, Y: 1.0}, Object{Type: roveapi.Object_RockLarge})
// Try and bump into the rock // Try and bump into the rock
vec, err := world.MoveRover(a, roveapi.Bearing_North) vec, err := world.MoveRover(a, roveapi.Bearing_North)
@ -277,7 +278,7 @@ func TestWorld_RoverRepair(t *testing.T) {
assert.Contains(t, newinfo.Logs[len(newinfo.Logs)-1].Text, "repair", "Rover logs should contain the repair") 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 // Check again that it can't repair past the max
world.Atlas.SetObject(pos, atlas.Object{Type: roveapi.Object_RockSmall}) world.Atlas.SetObject(pos, Object{Type: roveapi.Object_RockSmall})
o, err = world.RoverStash(a) o, err = world.RoverStash(a)
assert.NoError(t, err, "Failed to stash") assert.NoError(t, err, "Failed to stash")
assert.Equal(t, roveapi.Object_RockSmall, o, "Failed to get correct object") assert.Equal(t, roveapi.Object_RockSmall, o, "Failed to get correct object")
@ -308,7 +309,7 @@ func TestWorld_Charge(t *testing.T) {
// Ensure the path ahead is empty // Ensure the path ahead is empty
world.Atlas.SetTile(initialPos.Added(maths.BearingToVector(roveapi.Bearing_North)), roveapi.Tile_Rock) 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}) world.Atlas.SetObject(initialPos.Added(maths.BearingToVector(roveapi.Bearing_North)), Object{Type: roveapi.Object_ObjectUnknown})
// Try and move north (along unblocked path) // Try and move north (along unblocked path)
newPos, err := world.MoveRover(a, roveapi.Bearing_North) newPos, err := world.MoveRover(a, roveapi.Bearing_North)
@ -372,6 +373,8 @@ func TestWorld_Broadcast(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// Warp rovers near to eachother // Warp rovers near to eachother
world.Atlas.SetObject(maths.Vector{X: 0, Y: 0}, Object{Type: roveapi.Object_ObjectUnknown})
world.Atlas.SetObject(maths.Vector{X: 1, Y: 0}, Object{Type: roveapi.Object_ObjectUnknown})
assert.NoError(t, world.WarpRover(a, maths.Vector{X: 0, Y: 0})) assert.NoError(t, world.WarpRover(a, maths.Vector{X: 0, Y: 0}))
assert.NoError(t, world.WarpRover(b, maths.Vector{X: 1, Y: 0})) assert.NoError(t, world.WarpRover(b, maths.Vector{X: 1, Y: 0}))
@ -390,7 +393,7 @@ func TestWorld_Broadcast(t *testing.T) {
assert.Contains(t, rb.Logs[len(rb.Logs)-1].Text, "ABC", "Rover A should have logged it's broadcast") 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 // Warp B outside of the range of A
world.Atlas.SetObject(maths.Vector{X: ra.Range, Y: 0}, atlas.Object{Type: roveapi.Object_ObjectUnknown}) world.Atlas.SetObject(maths.Vector{X: ra.Range, Y: 0}, Object{Type: roveapi.Object_ObjectUnknown})
assert.NoError(t, world.WarpRover(b, maths.Vector{X: ra.Range, Y: 0})) assert.NoError(t, world.WarpRover(b, maths.Vector{X: ra.Range, Y: 0}))
// Broadcast from a again // Broadcast from a again
@ -407,7 +410,7 @@ func TestWorld_Broadcast(t *testing.T) {
assert.Contains(t, rb.Logs[len(rb.Logs)-1].Text, "XYZ", "Rover A should have logged it's broadcast") 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 // Warp B outside of the range of A
world.Atlas.SetObject(maths.Vector{X: ra.Range + 1, Y: 0}, atlas.Object{Type: roveapi.Object_ObjectUnknown}) world.Atlas.SetObject(maths.Vector{X: ra.Range + 1, Y: 0}, Object{Type: roveapi.Object_ObjectUnknown})
assert.NoError(t, world.WarpRover(b, maths.Vector{X: ra.Range + 1, Y: 0})) assert.NoError(t, world.WarpRover(b, maths.Vector{X: ra.Range + 1, Y: 0}))
// Broadcast from a again // Broadcast from a again

93
pkg/rove/worldgen.go Normal file
View file

@ -0,0 +1,93 @@
package rove
import (
"encoding/json"
"log"
"github.com/mdiluz/rove/pkg/maths"
"github.com/mdiluz/rove/proto/roveapi"
"github.com/ojrac/opensimplex-go"
)
// WorldGen describes a world gen algorythm
type WorldGen interface {
// GetTile generates a tile for a location
GetTile(v maths.Vector) roveapi.Tile
// GetObject generates an object for a location
GetObject(v maths.Vector) Object
}
// NoiseWorldGen returns a noise based world generator
type NoiseWorldGen struct {
// noise describes the noise function
noise opensimplex.Noise
}
// NewNoiseWorldGen creates a new noise based world generator
func NewNoiseWorldGen(seed int64) WorldGen {
return &NoiseWorldGen{
noise: opensimplex.New(seed),
}
}
const (
terrainNoiseScale = 6
rockNoiseScale = 3
)
// GetTile returns the chosen tile at a location
func (g *NoiseWorldGen) GetTile(v maths.Vector) roveapi.Tile {
t := g.noise.Eval2(float64(v.X)/terrainNoiseScale, float64(v.Y)/terrainNoiseScale)
switch {
case t > 0.5:
return roveapi.Tile_Gravel
case t > 0.05:
return roveapi.Tile_Sand
default:
return roveapi.Tile_Rock
}
}
// GetObject returns the chosen object at a location
func (g *NoiseWorldGen) GetObject(v maths.Vector) (obj Object) {
o := g.noise.Eval2(float64(v.X)/rockNoiseScale, float64(v.Y)/rockNoiseScale)
switch {
case o > 0.6:
obj.Type = roveapi.Object_RockLarge
case o > 0.5:
obj.Type = roveapi.Object_RockSmall
}
// Very rarely spawn a dormant rover
if obj.Type == roveapi.Object_ObjectUnknown {
// TODO: Make this better, ideally with noise
if v.X%25 == 0 && v.Y%25 == 0 && v.X != 0 && v.Y != 0 {
obj.Type = roveapi.Object_RoverDormant
}
}
// Post process any spawned objects
switch obj.Type {
case roveapi.Object_RoverDormant:
// Create the rover
r := DefaultRover()
// Set the rover variables
r.Pos = v
// For now, mark the log as corrupted
r.AddLogEntryf("log corrupted")
// Marshal the rover data into the object data
b, err := json.Marshal(r)
if err != nil {
log.Fatalf("couldn't marshal rover, should never fail: %s", err)
}
// Store the bytes
obj.Data = b
}
return obj
}

View file

@ -162,10 +162,12 @@ const (
Object_ObjectUnknown Object = 0 Object_ObjectUnknown Object = 0
// RoverLive represents a live rover // RoverLive represents a live rover
Object_RoverLive Object = 1 Object_RoverLive Object = 1
// RoverDormant describes a dormant rover
Object_RoverDormant Object = 2
// RockSmall is a small stashable rock // RockSmall is a small stashable rock
Object_RockSmall Object = 2 Object_RockSmall Object = 3
// RockLarge is a large blocking rock // RockLarge is a large blocking rock
Object_RockLarge Object = 3 Object_RockLarge Object = 4
) )
// Enum value maps for Object. // Enum value maps for Object.
@ -173,14 +175,16 @@ var (
Object_name = map[int32]string{ Object_name = map[int32]string{
0: "ObjectUnknown", 0: "ObjectUnknown",
1: "RoverLive", 1: "RoverLive",
2: "RockSmall", 2: "RoverDormant",
3: "RockLarge", 3: "RockSmall",
4: "RockLarge",
} }
Object_value = map[string]int32{ Object_value = map[string]int32{
"ObjectUnknown": 0, "ObjectUnknown": 0,
"RoverLive": 1, "RoverLive": 1,
"RockSmall": 2, "RoverDormant": 2,
"RockLarge": 3, "RockSmall": 3,
"RockLarge": 4,
} }
) )
@ -1269,39 +1273,40 @@ var file_roveapi_roveapi_proto_rawDesc = []byte{
0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x4e, 0x6f, 0x72, 0x74, 0x68, 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, 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, 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, 0x04, 0x2a, 0x5a, 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, 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, 0x0a, 0x09, 0x52, 0x6f, 0x76, 0x65, 0x72, 0x4c, 0x69, 0x76, 0x65, 0x10, 0x01, 0x12, 0x10, 0x0a,
0x09, 0x52, 0x6f, 0x63, 0x6b, 0x53, 0x6d, 0x61, 0x6c, 0x6c, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x0c, 0x52, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x74, 0x10, 0x02, 0x12,
0x52, 0x6f, 0x63, 0x6b, 0x4c, 0x61, 0x72, 0x67, 0x65, 0x10, 0x03, 0x2a, 0x37, 0x0a, 0x04, 0x54, 0x0d, 0x0a, 0x09, 0x52, 0x6f, 0x63, 0x6b, 0x53, 0x6d, 0x61, 0x6c, 0x6c, 0x10, 0x03, 0x12, 0x0d,
0x69, 0x6c, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x69, 0x6c, 0x65, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x0a, 0x09, 0x52, 0x6f, 0x63, 0x6b, 0x4c, 0x61, 0x72, 0x67, 0x65, 0x10, 0x04, 0x2a, 0x37, 0x0a,
0x77, 0x6e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x6f, 0x63, 0x6b, 0x10, 0x01, 0x12, 0x0a, 0x04, 0x54, 0x69, 0x6c, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x69, 0x6c, 0x65, 0x55, 0x6e, 0x6b,
0x0a, 0x06, 0x47, 0x72, 0x61, 0x76, 0x65, 0x6c, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x61, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x6f, 0x63, 0x6b, 0x10, 0x01,
0x6e, 0x64, 0x10, 0x03, 0x32, 0xcf, 0x02, 0x0a, 0x04, 0x52, 0x6f, 0x76, 0x65, 0x12, 0x4d, 0x0a, 0x12, 0x0a, 0x0a, 0x06, 0x47, 0x72, 0x61, 0x76, 0x65, 0x6c, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04,
0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x2e, 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, 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, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x41,
0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72, 0x6f, 0x76,
0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x08, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52,
0x70, 0x69, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x67, 0x00, 0x12, 0x3e, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x17, 0x2e, 0x72,
0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65,
0x3e, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x17, 0x2e, 0x72, 0x6f, 0x76, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e,
0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x00, 0x12, 0x38, 0x0a, 0x05, 0x52, 0x61, 0x64, 0x61, 0x72, 0x12, 0x15, 0x2e, 0x72, 0x6f, 0x76,
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x38, 0x0a, 0x05, 0x52, 0x61, 0x64, 0x61, 0x72, 0x12, 0x15, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x64, 0x61,
0x70, 0x69, 0x2e, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x53,
0x16, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e,
0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x72, 0x6f, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68,
0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x64, 0x69, 0x6c, 0x75, 0x7a, 0x2f, 0x72, 0x6f,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x76, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x64, 0x69, 0x6c, 0x75, 0x7a, 0x2f, 0x72, 0x6f, 0x76, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (

View file

@ -151,11 +151,14 @@ enum Object {
// RoverLive represents a live rover // RoverLive represents a live rover
RoverLive = 1; RoverLive = 1;
// RoverDormant describes a dormant rover
RoverDormant = 2;
// RockSmall is a small stashable rock // RockSmall is a small stashable rock
RockSmall = 2; RockSmall = 3;
// RockLarge is a large blocking rock // RockLarge is a large blocking rock
RockLarge = 3; RockLarge = 4;
} }
enum Tile { enum Tile {