From 50c970fea2d17d719bf5bc9966c994033aa207ec Mon Sep 17 00:00:00 2001 From: Marc Di Luzio Date: Tue, 2 Jun 2020 17:44:39 +0100 Subject: [PATCH] Add /spawn command to let an account spawn it's primary instance --- pkg/accounts/accounts.go | 30 ++++++++++++--- pkg/accounts/accounts_test.go | 29 +++++++++++++- pkg/game/world.go | 27 +++++++++++-- pkg/game/world_test.go | 2 + pkg/rove/rove_test.go | 2 + pkg/server/router.go | 72 +++++++++++++++++++++++++++++++++++ 6 files changed, 151 insertions(+), 11 deletions(-) diff --git a/pkg/accounts/accounts.go b/pkg/accounts/accounts.go index 603effa..ae72732 100644 --- a/pkg/accounts/accounts.go +++ b/pkg/accounts/accounts.go @@ -17,13 +17,16 @@ type Account struct { // Name simply describes the account and must be unique Name string `json:"name"` - // id represents a unique ID per account and is set one registered + // Id represents a unique ID per account and is set one registered Id uuid.UUID `json:"id"` + + // Primary represents the primary instance that this account owns + Primary uuid.UUID `json:"primary"` } // Represents the accountant data to store type accountantData struct { - Accounts []Account `json:"accounts"` + Accounts map[uuid.UUID]Account `json:"accounts"` } // Accountant manages a set of accounts @@ -36,6 +39,9 @@ type Accountant struct { func NewAccountant(dataPath string) *Accountant { return &Accountant{ dataPath: dataPath, + data: accountantData{ + Accounts: make(map[uuid.UUID]Account), + }, } } @@ -54,8 +60,8 @@ func (a *Accountant) RegisterAccount(acc Account) (Account, error) { } } - // Simply add the account to the list - a.data.Accounts = append(a.data.Accounts, acc) + // Simply add the account to the map + a.data.Accounts[acc.Id] = acc return acc, nil } @@ -84,7 +90,7 @@ func (a *Accountant) Load() error { // Save will save the accountant data out func (a *Accountant) Save() error { - if b, err := json.Marshal(a.data); err != nil { + if b, err := json.MarshalIndent(a.data, "", "\t"); err != nil { return err } else { if err := ioutil.WriteFile(a.path(), b, os.ModePerm); err != nil { @@ -93,3 +99,17 @@ func (a *Accountant) Save() error { } return nil } + +// AssignPrimary assigns primary ownership of an instance to an account +func (a *Accountant) AssignPrimary(account uuid.UUID, instance uuid.UUID) error { + + // Find the account matching the ID + if this, ok := a.data.Accounts[account]; ok { + this.Primary = instance + a.data.Accounts[account] = this + } else { + return fmt.Errorf("no account found for id: %s", account) + } + + return nil +} diff --git a/pkg/accounts/accounts_test.go b/pkg/accounts/accounts_test.go index 99b0b2d..9f77f57 100644 --- a/pkg/accounts/accounts_test.go +++ b/pkg/accounts/accounts_test.go @@ -3,6 +3,8 @@ package accounts import ( "os" "testing" + + "github.com/google/uuid" ) func TestNewAccountant(t *testing.T) { @@ -64,7 +66,7 @@ func TestAccountant_LoadSave(t *testing.T) { if len(accountant.data.Accounts) != 1 { t.Error("No new account made") - } else if accountant.data.Accounts[0].Name != name { + } else if accountant.data.Accounts[a.Id].Name != name { t.Error("New account created with wrong name") } @@ -87,7 +89,30 @@ func TestAccountant_LoadSave(t *testing.T) { // Verify we have the same account again if len(accountant.data.Accounts) != 1 { t.Error("No account after load") - } else if accountant.data.Accounts[0].Name != name { + } else if accountant.data.Accounts[a.Id].Name != name { t.Error("New account created with wrong name") } } + +func TestAccountant_AssignPrimary(t *testing.T) { + accountant := NewAccountant(os.TempDir()) + if len(accountant.data.Accounts) != 0 { + t.Error("New accountant created with non-zero account number") + } + + name := "one" + a := Account{Name: name} + a, err := accountant.RegisterAccount(a) + if err != nil { + t.Error(err) + } + + inst := uuid.New() + + err = accountant.AssignPrimary(a.Id, inst) + if err != nil { + t.Error("Failed to set primary for created account") + } else if accountant.data.Accounts[a.Id].Primary != inst { + t.Error("Primary for assigned account is incorrect") + } +} diff --git a/pkg/game/world.go b/pkg/game/world.go index f03d99c..1209e71 100644 --- a/pkg/game/world.go +++ b/pkg/game/world.go @@ -1,20 +1,30 @@ package game -import "github.com/google/uuid" +import ( + "fmt" + + "github.com/google/uuid" +) // World describes a self contained universe and everything in it type World struct { - instances []Instance + instances map[uuid.UUID]Instance } // Instance describes a single entity or instance of an entity in the world type Instance struct { + // id is a unique ID for this instance id uuid.UUID + + // pos represents where this instance is in the world + pos Position } // NewWorld creates a new world object func NewWorld() *World { - return &World{} + return &World{ + instances: make(map[uuid.UUID]Instance), + } } // Adds an instance to the game @@ -27,7 +37,16 @@ func (w *World) CreateInstance() uuid.UUID { } // Append the instance to the list - w.instances = append(w.instances, instance) + w.instances[id] = instance return id } + +// GetPosition returns the position of a given instance +func (w World) GetPosition(id uuid.UUID) (Position, error) { + if i, ok := w.instances[id]; ok { + return i.pos, nil + } else { + return Position{}, fmt.Errorf("no instance matching id") + } +} diff --git a/pkg/game/world_test.go b/pkg/game/world_test.go index 504b8ee..6614257 100644 --- a/pkg/game/world_test.go +++ b/pkg/game/world_test.go @@ -20,5 +20,7 @@ func TestWorld_CreateInstance(t *testing.T) { // Basic duplicate check if a == b { t.Errorf("Created identical instances") + } else if len(world.instances) != 2 { + t.Errorf("Incorrect number of instances created") } } diff --git a/pkg/rove/rove_test.go b/pkg/rove/rove_test.go index 54c2e2f..35cd8c3 100644 --- a/pkg/rove/rove_test.go +++ b/pkg/rove/rove_test.go @@ -15,6 +15,8 @@ func TestStatus(t *testing.T) { t.Errorf("Status returned error: %s", err) } else if !status.Ready { t.Error("Server did not return that it was ready") + } else if len(status.Version) == 0 { + t.Error("Server returned blank version") } } diff --git a/pkg/server/router.go b/pkg/server/router.go index ebf1951..370f684 100644 --- a/pkg/server/router.go +++ b/pkg/server/router.go @@ -5,7 +5,9 @@ import ( "fmt" "net/http" + "github.com/google/uuid" "github.com/mdiluz/rove/pkg/accounts" + "github.com/mdiluz/rove/pkg/game" "github.com/mdiluz/rove/pkg/version" ) @@ -28,6 +30,10 @@ func (s *Server) SetUpRouter() { path: "/register", handler: s.HandleRegister, }, + { + path: "/spawn", + handler: s.HandleSpawn, + }, } // Set up the handlers @@ -138,3 +144,69 @@ func (s *Server) HandleRegister(w http.ResponseWriter, r *http.Request) { // Reply with the current status json.NewEncoder(w).Encode(response) } + +// SpawnData is the data to be sent for the spawn command +type SpawnData struct { + BasicAccountData +} + +// SpawnResponse is the data to respond with on a spawn command +type SpawnResponse struct { + BasicResponse + + Position game.Position `json:"position"` +} + +// HandleSpawn will spawn the player entity for the associated account +func (s *Server) HandleSpawn(w http.ResponseWriter, r *http.Request) { + fmt.Printf("%s\t%s\n", r.Method, r.RequestURI) + + // Set up the response + var response = SpawnResponse{ + BasicResponse: BasicResponse{ + Success: false, + }, + } + + // Verify we're hit with a get request + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + // Pull out the incoming info + var data SpawnData + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + fmt.Printf("Failed to decode json: %s\n", err) + + response.Error = err.Error() + } else if len(data.Id) == 0 { + response.Error = "No account ID provided" + } else if id, err := uuid.Parse(data.Id); err != nil { + response.Error = "Provided account ID was invalid" + } else { + // log the data sent + fmt.Printf("\tdata: %v\n", data) + + // Create a new instance + inst := s.world.CreateInstance() + if pos, err := s.world.GetPosition(inst); err != nil { + response.Error = fmt.Sprint("No position found for created instance") + } else { + if err := s.accountant.AssignPrimary(id, inst); err != nil { + response.Error = err.Error() + } else { + response.Success = true + response.Position = pos + } + } + } + + // Be a good citizen and set the header for the return + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + + // Reply with the current status + json.NewEncoder(w).Encode(response) +}