From e5d5d123a69559177baa1a087932750147e855a0 Mon Sep 17 00:00:00 2001 From: Marc Di Luzio Date: Wed, 3 Jun 2020 18:12:08 +0100 Subject: [PATCH] Add the concept of commands to the world and executing them --- pkg/game/command.go | 23 ++++++++++++++++++ pkg/game/command_test.go | 50 ++++++++++++++++++++++++++++++++++++++ pkg/game/geom.go | 15 +++++++++--- pkg/game/world.go | 52 ++++++++++++++++++++++++++++++++-------- pkg/game/world_test.go | 37 ++++++++++++++++++++++++---- pkg/server/router.go | 5 ++-- 6 files changed, 163 insertions(+), 19 deletions(-) create mode 100644 pkg/game/command.go create mode 100644 pkg/game/command_test.go diff --git a/pkg/game/command.go b/pkg/game/command.go new file mode 100644 index 0000000..ef7bece --- /dev/null +++ b/pkg/game/command.go @@ -0,0 +1,23 @@ +package game + +import "github.com/google/uuid" + +// A command is simply a function that acts on the a given instance in the world +type Command func() error + +// CommandMove will move the instance in question +func (w *World) CommandMove(id uuid.UUID, vec Vector) Command { + return func() error { + // Move the instance + _, err := w.MovePosition(id, vec) + return err + } +} + +// CommandSpawn +// TODO: Two spawn commands with the same id could trigger a fail later on, we should prevent that somehow +func (w *World) CommandSpawn(id uuid.UUID) Command { + return func() error { + return w.Spawn(id) + } +} diff --git a/pkg/game/command_test.go b/pkg/game/command_test.go new file mode 100644 index 0000000..0bb51b3 --- /dev/null +++ b/pkg/game/command_test.go @@ -0,0 +1,50 @@ +package game + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestCommand_Spawn(t *testing.T) { + world := NewWorld() + a := uuid.New() + + spawnCommand := world.CommandSpawn(a) + assert.NoError(t, world.Execute(spawnCommand), "Failed to execute spawn command") + + instance, ok := world.Instances[a] + assert.True(t, ok, "No new instance in world") + assert.Equal(t, a, instance.id, "New instance has incorrect id") +} + +func TestCommand_Move(t *testing.T) { + world := NewWorld() + a := uuid.New() + assert.NoError(t, world.Spawn(a), "Failed to spawn") + + pos := Vector{ + X: 1.0, + Y: 2.0, + Z: 3.0, + } + + err := world.SetPosition(a, pos) + assert.NoError(t, err, "Failed to set position for instance") + + move := Vector{ + X: 3.0, + Y: 2.0, + Z: 1.0, + } + + // Try the move command + moveCommand := world.CommandMove(a, move) + assert.NoError(t, world.Execute(moveCommand), "Failed to execute move command") + + newpos, err := world.GetPosition(a) + assert.NoError(t, err, "Failed to set position for instance") + pos.Add(move) + assert.Equal(t, pos, newpos, "Failed to correctly set position for instance") +} diff --git a/pkg/game/geom.go b/pkg/game/geom.go index b41a8de..585799f 100644 --- a/pkg/game/geom.go +++ b/pkg/game/geom.go @@ -1,6 +1,15 @@ package game -type Position struct { - X int `json:"x"` - Y int `json:"y"` +// Vector desribes a 3D vector +type Vector struct { + X float64 `json:"x"` + Y float64 `json:"y"` + Z float64 `json:"z"` +} + +// Add adds one vector to another +func (v *Vector) Add(v2 Vector) { + v.X += v2.X + v.Y += v2.Y + v.Z += v2.Z } diff --git a/pkg/game/world.go b/pkg/game/world.go index c2639f0..b3b00ad 100644 --- a/pkg/game/world.go +++ b/pkg/game/world.go @@ -8,10 +8,8 @@ import ( // World describes a self contained universe and everything in it type World struct { + // Instances is a map of all the instances in the game Instances map[uuid.UUID]Instance `json:"instances"` - - // dataPath is the location for the data to be stored - dataPath string } // Instance describes a single entity or instance of an entity in the world @@ -20,7 +18,7 @@ type Instance struct { id uuid.UUID // pos represents where this instance is in the world - pos Position + pos Vector } const kWorldFileName = "rove-world.json" @@ -32,9 +30,11 @@ func NewWorld() *World { } } -// Adds an instance to the game -func (w *World) CreateInstance() uuid.UUID { - id := uuid.New() +// Spawn adds an instance to the game +func (w *World) Spawn(id uuid.UUID) error { + if _, ok := w.Instances[id]; ok { + return fmt.Errorf("instance with id %s already exists in world", id) + } // Initialise the instance instance := Instance{ @@ -44,7 +44,7 @@ func (w *World) CreateInstance() uuid.UUID { // Append the instance to the list w.Instances[id] = instance - return id + return nil } // Removes an instance from the game @@ -58,10 +58,42 @@ func (w *World) DestroyInstance(id uuid.UUID) error { } // GetPosition returns the position of a given instance -func (w World) GetPosition(id uuid.UUID) (Position, error) { +func (w World) GetPosition(id uuid.UUID) (Vector, error) { if i, ok := w.Instances[id]; ok { return i.pos, nil } else { - return Position{}, fmt.Errorf("no instance matching id") + return Vector{}, fmt.Errorf("no instance matching id") } } + +// SetPosition sets an instances position +func (w *World) SetPosition(id uuid.UUID, pos Vector) error { + if i, ok := w.Instances[id]; ok { + i.pos = pos + w.Instances[id] = i + return nil + } else { + return fmt.Errorf("no instance matching id") + } +} + +// SetPosition sets an instances position +func (w *World) MovePosition(id uuid.UUID, vec Vector) (Vector, error) { + if i, ok := w.Instances[id]; ok { + i.pos.Add(vec) + w.Instances[id] = i + return i.pos, nil + } else { + return Vector{}, fmt.Errorf("no instance matching id") + } +} + +// Execute will run the commands given +func (w *World) Execute(commands ...Command) error { + for _, c := range commands { + if err := c(); err != nil { + return err + } + } + return nil +} diff --git a/pkg/game/world_test.go b/pkg/game/world_test.go index af29246..b351377 100644 --- a/pkg/game/world_test.go +++ b/pkg/game/world_test.go @@ -3,6 +3,7 @@ package game import ( "testing" + "github.com/google/uuid" "github.com/stretchr/testify/assert" ) @@ -16,8 +17,10 @@ func TestNewWorld(t *testing.T) { func TestWorld_CreateInstance(t *testing.T) { world := NewWorld() - a := world.CreateInstance() - b := world.CreateInstance() + a := uuid.New() + b := uuid.New() + assert.NoError(t, world.Spawn(a), "Failed to spawn") + assert.NoError(t, world.Spawn(b), "Failed to spawn") // Basic duplicate check if a == b { @@ -29,8 +32,10 @@ func TestWorld_CreateInstance(t *testing.T) { func TestWorld_DestroyInstance(t *testing.T) { world := NewWorld() - a := world.CreateInstance() - b := world.CreateInstance() + a := uuid.New() + b := uuid.New() + assert.NoError(t, world.Spawn(a), "Failed to spawn") + assert.NoError(t, world.Spawn(b), "Failed to spawn") err := world.DestroyInstance(a) assert.NoError(t, err, "Error returned from instance destroy") @@ -42,3 +47,27 @@ func TestWorld_DestroyInstance(t *testing.T) { t.Error("Remaining instance is incorrect") } } + +func TestWorld_GetSetMovePosition(t *testing.T) { + world := NewWorld() + a := uuid.New() + assert.NoError(t, world.Spawn(a), "Failed to spawn") + + pos := Vector{ + X: 1.0, + Y: 2.0, + Z: 3.0, + } + + err := world.SetPosition(a, pos) + assert.NoError(t, err, "Failed to set position for instance") + + newpos, err := world.GetPosition(a) + assert.NoError(t, err, "Failed to set position for instance") + assert.Equal(t, pos, newpos, "Failed to correctly set position for instance") + + newpos, err = world.MovePosition(a, pos) + assert.NoError(t, err, "Failed to set position for instance") + pos.Add(pos) + assert.Equal(t, pos, newpos, "Failed to correctly move position for instance") +} diff --git a/pkg/server/router.go b/pkg/server/router.go index 4131be9..42dfe5f 100644 --- a/pkg/server/router.go +++ b/pkg/server/router.go @@ -155,7 +155,7 @@ type SpawnData struct { type SpawnResponse struct { BasicResponse - Position game.Position `json:"position"` + Position game.Vector `json:"position"` } // HandleSpawn will spawn the player entity for the associated account @@ -192,7 +192,8 @@ func (s *Server) HandleSpawn(w http.ResponseWriter, r *http.Request) { fmt.Printf("\tspawn data: %v\n", data) // Create a new instance - inst := s.world.CreateInstance() + inst := uuid.New() + s.world.Spawn(inst) if pos, err := s.world.GetPosition(inst); err != nil { response.Error = fmt.Sprint("No position found for created instance")