diff --git a/pkg/accounts/accounts.go b/pkg/accounts/accounts.go index 37effec..99fa47e 100644 --- a/pkg/accounts/accounts.go +++ b/pkg/accounts/accounts.go @@ -71,3 +71,12 @@ func (a *Accountant) AssignPrimary(account uuid.UUID, instance uuid.UUID) error return nil } + +// GetPrimary gets the primary instance for the account +func (a *Accountant) GetPrimary(account uuid.UUID) (uuid.UUID, error) { + // Find the account matching the ID + if this, ok := a.Accounts[account]; ok { + return this.Primary, nil + } + return uuid.UUID{}, fmt.Errorf("no account found for id: %s", account) +} diff --git a/pkg/accounts/accounts_test.go b/pkg/accounts/accounts_test.go index bb6c9a7..6597ab5 100644 --- a/pkg/accounts/accounts_test.go +++ b/pkg/accounts/accounts_test.go @@ -48,7 +48,7 @@ func TestAccountant_RegisterAccount(t *testing.T) { } } -func TestAccountant_AssignPrimary(t *testing.T) { +func TestAccountant_AssignGetPrimary(t *testing.T) { accountant := NewAccountant() if len(accountant.Accounts) != 0 { t.Error("New accountant created with non-zero account number") @@ -67,5 +67,9 @@ func TestAccountant_AssignPrimary(t *testing.T) { t.Error("Failed to set primary for created account") } else if accountant.Accounts[a.Id].Primary != inst { t.Error("Primary for assigned account is incorrect") + } else if id, err := accountant.GetPrimary(a.Id); err != nil { + t.Error("Failed to get primary for account") + } else if id != inst { + t.Error("Fetched primary is incorrect for account") } } diff --git a/pkg/server/router.go b/pkg/server/router.go index 42dfe5f..0898cdd 100644 --- a/pkg/server/router.go +++ b/pkg/server/router.go @@ -33,6 +33,10 @@ func (s *Server) SetUpRouter() { path: "/spawn", handler: s.HandleSpawn, }, + { + path: "/commands", + handler: s.HandleCommands, + }, } // Set up the handlers @@ -192,25 +196,93 @@ func (s *Server) HandleSpawn(w http.ResponseWriter, r *http.Request) { fmt.Printf("\tspawn data: %v\n", data) // Create a new instance - 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") - + if pos, _, err := s.SpawnPrimary(id); err != nil { + response.Error = err.Error() } else { - if err := s.accountant.AssignPrimary(id, inst); err != nil { - response.Error = err.Error() - - // Try and clear up the instance - if err := s.world.DestroyInstance(inst); err != nil { - fmt.Printf("Failed to destroy instance after failed primary assign: %s", err) - } - - } else { - // Reply with valid data - response.Success = true - response.Position = pos - } + 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) + + // Log the response + fmt.Printf("\tresponse: %+v\n", response) + + // Reply with the current status + json.NewEncoder(w).Encode(response) +} + +const ( + // CommandMove describes a single move command + CommandMove = "move" +) + +// Command describes a single command to execute +// it contains the type, and then any members used for each command type +type Command struct { + // Command is the main command string + Command string `json:"command"` + + // Used for CommandMove + Vector game.Vector `json:"vector"` +} + +// CommandsData is a set of commands to execute in order +type CommandsData struct { + BasicAccountData + Commands []Command `json:"commands"` +} + +// HandleSpawn will spawn the player entity for the associated account +func (s *Server) HandleCommands(w http.ResponseWriter, r *http.Request) { + // Verify we're hit with a get request + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + fmt.Printf("%s\t%s\n", r.Method, r.RequestURI) + + // Set up the response + var response = BasicResponse{ + Success: false, + } + + // Pull out the incoming info + var data CommandsData + if err := json.NewDecoder(r.Body).Decode(&data); 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 = fmt.Sprintf("Provided account ID was invalid: %s", err) + + } else if inst, err := s.accountant.GetPrimary(id); err != nil { + response.Error = fmt.Sprintf("Provided account has no primary: %s", err) + } else { + // log the data sent + fmt.Printf("\tcommands data: %v\n", data) + + // Iterate through the commands to generate all game commands + var cmds []game.Command + for _, c := range data.Commands { + switch c.Command { + case CommandMove: + cmds = append(cmds, s.world.CommandMove(inst, c.Vector)) + } + } + + // Execute the commands + if err := s.world.Execute(cmds...); err != nil { + response.Error = fmt.Sprintf("Failed to execute commands: %s", err) + } else { + response.Success = true } } diff --git a/pkg/server/router_test.go b/pkg/server/router_test.go index 21440c5..b36a1aa 100644 --- a/pkg/server/router_test.go +++ b/pkg/server/router_test.go @@ -7,6 +7,7 @@ import ( "net/http/httptest" "testing" + "github.com/mdiluz/rove/pkg/game" "github.com/stretchr/testify/assert" ) @@ -71,3 +72,45 @@ func TestHandleSpawn(t *testing.T) { t.Errorf("got false for /spawn") } } + +func TestHandleCommands(t *testing.T) { + s := NewServer() + a, err := s.accountant.RegisterAccount("test") + assert.NoError(t, err, "Error registering account") + + // Spawn the primary instance for the account + _, inst, err := s.SpawnPrimary(a.Id) + + move := game.Vector{X: 1, Y: 2, Z: 3} + + data := CommandsData{ + BasicAccountData: BasicAccountData{Id: a.Id.String()}, + Commands: []Command{ + { + Command: CommandMove, + Vector: move, + }, + }, + } + + b, err := json.Marshal(data) + assert.NoError(t, err, "Error marshalling data") + + request, _ := http.NewRequest(http.MethodPost, "/commands", bytes.NewReader(b)) + response := httptest.NewRecorder() + + s.HandleCommands(response, request) + + var status BasicResponse + json.NewDecoder(response.Body).Decode(&status) + + if status.Success != true { + t.Errorf("got false for /commands") + } + + if pos, err := s.world.GetPosition(inst); err != nil { + t.Error("Couldn't get position for the primary instance") + } else if pos != move { + t.Error("Mismatched position after commands") + } +} diff --git a/pkg/server/server.go b/pkg/server/server.go index 37ff0ae..6d666a5 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/google/uuid" "github.com/gorilla/mux" "github.com/mdiluz/rove/pkg/accounts" "github.com/mdiluz/rove/pkg/game" @@ -133,3 +134,24 @@ func (s *Server) Close() error { } return nil } + +// SpawnPrimary spawns the primary instance for an account +func (s *Server) SpawnPrimary(accountid uuid.UUID) (game.Vector, uuid.UUID, error) { + inst := uuid.New() + s.world.Spawn(inst) + if pos, err := s.world.GetPosition(inst); err != nil { + return game.Vector{}, uuid.UUID{}, fmt.Errorf("No position found for created instance") + + } else { + if err := s.accountant.AssignPrimary(accountid, inst); err != nil { + // Try and clear up the instance + if err := s.world.DestroyInstance(inst); err != nil { + fmt.Printf("Failed to destroy instance after failed primary assign: %s", err) + } + + return game.Vector{}, uuid.UUID{}, err + } else { + return pos, inst, nil + } + } +}