Add /spawn command to let an account spawn it's primary instance

This commit is contained in:
Marc Di Luzio 2020-06-02 17:44:39 +01:00
parent 0a1f7a37c4
commit 50c970fea2
6 changed files with 151 additions and 11 deletions

View file

@ -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
}

View file

@ -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")
}
}

View file

@ -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")
}
}

View file

@ -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")
}
}

View file

@ -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")
}
}

View file

@ -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)
}