Migrate to gRPC rather than REST with swagger

Will also be adding in a RESTful endpoint to the server as well so it can consume both types
This commit is contained in:
Marc Di Luzio 2020-06-12 22:51:18 +01:00
parent b815284199
commit 7ababb79f6
23 changed files with 1110 additions and 1101 deletions

View file

@ -22,11 +22,6 @@ jobs:
- name: Install dict
run: sudo apt-get update && sudo apt-get install wamerican
- name: Install swagger
run: |
sudo curl -o /usr/local/bin/swagger -L'#' https://github.com/go-swagger/go-swagger/releases/download/v0.23.0/swagger_linux_amd64
sudo chmod +x /usr/local/bin/swagger
- name: Check out repo
uses: actions/checkout@v2

View file

@ -1,8 +0,0 @@
FROM quay.io/goswagger/swagger:latest
LABEL maintainer="Marc Di Luzio <marc.diluzio@gmail.com>"
WORKDIR /app
COPY . .
CMD [ "serve", "swagger.yml", "--no-open" ]

View file

@ -10,6 +10,7 @@ install:
gen:
protoc --proto_path pkg/accounts --go_out=plugins=grpc:pkg/accounts/ --go_opt=paths=source_relative pkg/accounts/accounts.proto
protoc --proto_path pkg/rove --go_out=plugins=grpc:pkg/rove/ --go_opt=paths=source_relative pkg/rove/rove.proto
test:
@echo Unit tests
@ -19,9 +20,6 @@ test:
docker-compose up --build --exit-code-from=rove-tests --abort-on-container-exit rove-tests
go tool cover -html=/tmp/coverage-data/c.out -o /tmp/coverage.html
@echo Validating swagger spec
swagger validate swagger.yml
@echo Done, coverage data can be found in /tmp/coverage.html
.PHONY: build install test gen

View file

@ -2,7 +2,6 @@ package main
import (
"context"
"fmt"
"log"
"net"
"os"
@ -34,20 +33,20 @@ func (a *accountantServer) Register(ctx context.Context, in *accounts.RegisterIn
log.Printf("Registering account: %s\n", in.Name)
if _, err := a.accountant.RegisterAccount(in.Name); err != nil {
log.Printf("Error: %s\n", err)
return &accounts.RegisterResponse{Success: false, Error: fmt.Sprintf("error registering account: %s", err)}, nil
return nil, err
}
// Save out the accounts
if err := persistence.Save("accounts", a.accountant); err != nil {
log.Printf("Error: %s\n", err)
return &accounts.RegisterResponse{Success: false, Error: fmt.Sprintf("failed to save accounts: %s", err)}, nil
return nil, err
}
return &accounts.RegisterResponse{Success: true}, nil
return &accounts.RegisterResponse{}, nil
}
// AssignData assigns a key value pair to an account
func (a *accountantServer) AssignValue(_ context.Context, in *accounts.DataKeyValue) (*accounts.Response, error) {
func (a *accountantServer) AssignValue(_ context.Context, in *accounts.DataKeyValue) (*accounts.DataKeyResponse, error) {
a.sync.RLock()
defer a.sync.RUnlock()
@ -56,10 +55,10 @@ func (a *accountantServer) AssignValue(_ context.Context, in *accounts.DataKeyVa
err := a.accountant.AssignData(in.Account, in.Key, in.Value)
if err != nil {
log.Printf("Error: %s\n", err)
return &accounts.Response{Success: false, Error: err.Error()}, nil
return nil, err
}
return &accounts.Response{Success: true}, nil
return &accounts.DataKeyResponse{}, nil
}
@ -73,10 +72,10 @@ func (a *accountantServer) GetValue(_ context.Context, in *accounts.DataKey) (*a
data, err := a.accountant.GetValue(in.Account, in.Key)
if err != nil {
log.Printf("Error: %s\n", err)
return &accounts.DataResponse{Success: false, Error: err.Error()}, nil
return nil, err
}
return &accounts.DataResponse{Success: true, Value: data}, nil
return &accounts.DataResponse{Value: data}, nil
}
@ -117,7 +116,7 @@ func main() {
// Serve the RPC server
log.Printf("Serving accountant on %s\n", address)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to server gRPC: %s", err)
log.Fatalf("failed to serve gRPC: %s", err)
}
// Save out the accountant data

View file

@ -1,91 +0,0 @@
// +build integration
package internal
import (
"os"
"testing"
"github.com/google/uuid"
"github.com/mdiluz/rove/pkg/game"
"github.com/mdiluz/rove/pkg/rove"
"github.com/stretchr/testify/assert"
)
const (
defaultAddress = "localhost:8080"
)
var serv = func() rove.Server {
var address = os.Getenv("ROVE_SERVER_ADDRESS")
if len(address) == 0 {
address = defaultAddress
}
return rove.Server(address)
}()
func TestServer_Status(t *testing.T) {
status, err := serv.Status()
assert.NoError(t, err)
assert.True(t, status.Ready)
assert.NotZero(t, len(status.Version))
}
func TestServer_Register(t *testing.T) {
d1 := rove.RegisterData{
Name: uuid.New().String(),
}
_, err := serv.Register(d1)
assert.NoError(t, err)
d2 := rove.RegisterData{
Name: uuid.New().String(),
}
_, err = serv.Register(d2)
assert.NoError(t, err)
_, err = serv.Register(d1)
assert.Error(t, err)
}
func TestServer_Command(t *testing.T) {
d1 := rove.RegisterData{
Name: uuid.New().String(),
}
_, err := serv.Register(d1)
assert.NoError(t, err)
c := rove.CommandData{
Commands: []game.Command{
{
Command: game.CommandMove,
Bearing: "N",
Duration: 1,
},
},
}
_, err = serv.Command(d1.Name, c)
assert.NoError(t, err)
}
func TestServer_Radar(t *testing.T) {
d1 := rove.RegisterData{
Name: uuid.New().String(),
}
_, err := serv.Register(d1)
assert.NoError(t, err)
_, err = serv.Radar(d1.Name)
assert.NoError(t, err)
}
func TestServer_Rover(t *testing.T) {
d1 := rove.RegisterData{
Name: uuid.New().String(),
}
_, err := serv.Register(d1)
assert.NoError(t, err)
_, err = serv.Rover(d1.Name)
assert.NoError(t, err)
}

View file

@ -2,69 +2,26 @@ package internal
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
"github.com/golang/protobuf/ptypes/empty"
"github.com/google/uuid"
"github.com/mdiluz/rove/pkg/accounts"
"github.com/mdiluz/rove/pkg/game"
"github.com/mdiluz/rove/pkg/rove"
"github.com/mdiluz/rove/pkg/version"
"google.golang.org/grpc"
)
// Handler describes a function that handles any incoming request and can respond
type Handler func(*Server, map[string]string, io.ReadCloser) (interface{}, error)
// Route defines the information for a single path->function route
type Route struct {
path string
method string
handler Handler
}
// Routes is an array of all the Routes
var Routes = []Route{
{
path: "/status",
method: http.MethodGet,
handler: HandleStatus,
},
{
path: "/register",
method: http.MethodPost,
handler: HandleRegister,
},
{
path: "/{account}/command",
method: http.MethodPost,
handler: HandleCommand,
},
{
path: "/{account}/radar",
method: http.MethodGet,
handler: HandleRadar,
},
{
path: "/{account}/rover",
method: http.MethodGet,
handler: HandleRover,
},
}
// HandleStatus handles the /status request
func HandleStatus(s *Server, vars map[string]string, b io.ReadCloser) (interface{}, error) {
// Simply return the current server status
response := rove.StatusResponse{
func (s *Server) Status(context.Context, *empty.Empty) (*rove.StatusResponse, error) {
response := &rove.StatusResponse{
Ready: true,
Version: version.Version,
Tick: s.tick,
Tick: int32(s.tick),
}
// TODO: Verify the accountant is up and ready too
// If there's a schedule, respond with it
if len(s.schedule.Entries()) > 0 {
response.NextTick = s.schedule.Entries()[0].Next.Format("15:04:05")
@ -73,31 +30,15 @@ func HandleStatus(s *Server, vars map[string]string, b io.ReadCloser) (interface
return response, nil
}
// HandleRegister handles /register endpoint
func HandleRegister(s *Server, vars map[string]string, b io.ReadCloser) (interface{}, error) {
var response = rove.RegisterResponse{}
// Decode the registration info, verify it and register the account
var data rove.RegisterData
err := json.NewDecoder(b).Decode(&data)
if err != nil {
log.Printf("Failed to decode json: %s\n", err)
return BadRequestError{Error: err.Error()}, nil
} else if len(data.Name) == 0 {
return BadRequestError{Error: "cannot register empty name"}, nil
func (s *Server) Register(ctx context.Context, req *rove.RegisterRequest) (*empty.Empty, error) {
if len(req.Name) == 0 {
return nil, fmt.Errorf("empty account name")
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
reg := accounts.RegisterInfo{Name: data.Name}
if acc, err := s.accountant.Register(ctx, &reg, grpc.WaitForReady(true)); err != nil {
return nil, fmt.Errorf("gRPC failed to contact accountant: %s", err)
} else if !acc.Success {
return BadRequestError{Error: acc.Error}, nil
if _, err := s.accountant.Register(ctx, &accounts.RegisterInfo{Name: req.Name}, grpc.WaitForReady(true)); err != nil {
return nil, err
} else if _, _, err := s.SpawnRoverForAccount(data.Name); err != nil {
} else if _, _, err := s.SpawnRoverForAccount(req.Name); err != nil {
return nil, fmt.Errorf("failed to spawn rover for account: %s", err)
} else if err := s.SaveWorld(); err != nil {
@ -105,66 +46,48 @@ func HandleRegister(s *Server, vars map[string]string, b io.ReadCloser) (interfa
}
log.Printf("register response:%+v\n", response)
return response, nil
return &empty.Empty{}, nil
}
// HandleSpawn will spawn the player entity for the associated account
func HandleCommand(s *Server, vars map[string]string, b io.ReadCloser) (interface{}, error) {
var response = rove.CommandResponse{}
func (s *Server) Rover(ctx context.Context, req *rove.RoverRequest) (*rove.RoverResponse, error) {
response := &rove.RoverResponse{}
if len(req.Account) == 0 {
return nil, fmt.Errorf("empty account name")
id := vars["account"]
// Decode the commands, verify them and the account, and execute the commands
var data rove.CommandData
if err := json.NewDecoder(b).Decode(&data); err != nil {
log.Printf("Failed to decode json: %s\n", err)
return BadRequestError{Error: err.Error()}, nil
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
key := accounts.DataKey{Account: id, Key: "rover"}
if len(id) == 0 {
return BadRequestError{Error: "no account ID provided"}, nil
} else if resp, err := s.accountant.GetValue(ctx, &key); err != nil {
} else if resp, err := s.accountant.GetValue(ctx, &accounts.DataKey{Account: req.Account, Key: "rover"}); err != nil {
return nil, fmt.Errorf("gRPC failed to contact accountant: %s", err)
} else if !resp.Success {
return BadRequestError{Error: resp.Error}, nil
} else if id, err := uuid.Parse(resp.Value); err != nil {
return nil, fmt.Errorf("account had invalid rover ID: %s", resp.Value)
} else if err := s.world.Enqueue(id, data.Commands...); err != nil {
return BadRequestError{Error: err.Error()}, nil
} else if attrib, err := s.world.RoverAttributes(id); err != nil {
return nil, fmt.Errorf("error getting rover attributes: %s", err)
} else {
response = &rove.RoverResponse{
Name: attrib.Name,
Position: &rove.Vector{
X: int32(attrib.Pos.X),
Y: int32(attrib.Pos.Y),
},
}
}
log.Printf("command response \taccount:%s\tresponse:%+v\n", id, response)
return response, nil
}
// HandleRadar handles the radar request
func HandleRadar(s *Server, vars map[string]string, b io.ReadCloser) (interface{}, error) {
var response = rove.RadarResponse{}
func (s *Server) Radar(ctx context.Context, req *rove.RadarRequest) (*rove.RadarResponse, error) {
if len(req.Account) == 0 {
return nil, fmt.Errorf("empty account name")
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
id := vars["account"]
key := accounts.DataKey{Account: id, Key: "rover"}
if len(id) == 0 {
return BadRequestError{Error: "no account ID provided"}, nil
response := &rove.RadarResponse{}
} else if resp, err := s.accountant.GetValue(ctx, &key); err != nil {
resp, err := s.accountant.GetValue(ctx, &accounts.DataKey{Account: req.Account, Key: "rover"})
if err != nil {
return nil, fmt.Errorf("gRPC failed to contact accountant: %s", err)
}
} else if !resp.Success {
return BadRequestError{Error: resp.Error}, nil
} else if id, err := uuid.Parse(resp.Value); err != nil {
if id, err := uuid.Parse(resp.Value); err != nil {
return nil, fmt.Errorf("account had invalid rover ID: %s", resp.Value)
} else if attrib, err := s.world.RoverAttributes(id); err != nil {
@ -175,40 +98,38 @@ func HandleRadar(s *Server, vars map[string]string, b io.ReadCloser) (interface{
} else {
response.Tiles = radar
response.Range = attrib.Range
response.Range = int32(attrib.Range)
}
log.Printf("radar response \taccount:%s\tresponse:%+v\n", id, response)
return response, nil
}
// HandleRover handles the rover request
func HandleRover(s *Server, vars map[string]string, b io.ReadCloser) (interface{}, error) {
var response = rove.RoverResponse{}
func (s *Server) Commands(ctx context.Context, req *rove.CommandsRequest) (*empty.Empty, error) {
if len(req.Account) == 0 {
return nil, fmt.Errorf("empty account")
}
resp, err := s.accountant.GetValue(ctx, &accounts.DataKey{Account: req.Account, Key: "rover"})
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
id := vars["account"]
key := accounts.DataKey{Account: id, Key: "rover"}
if len(id) == 0 {
return BadRequestError{Error: "no account ID provided"}, nil
if err != nil {
return nil, err
}
} else if resp, err := s.accountant.GetValue(ctx, &key); err != nil {
return nil, fmt.Errorf("gRPC failed to contact accountant: %s", err)
} else if !resp.Success {
return BadRequestError{Error: resp.Error}, nil
} else if id, err := uuid.Parse(resp.Value); err != nil {
id, err := uuid.Parse(resp.Value)
if err != nil {
return nil, fmt.Errorf("account had invalid rover ID: %s", resp.Value)
} else if attrib, err := s.world.RoverAttributes(id); err != nil {
return nil, fmt.Errorf("error getting rover attributes: %s", err)
} else {
response.Attributes = attrib
}
log.Printf("rover response \taccount:%s\tresponse:%+v\n", id, response)
return response, nil
var cmds []game.Command
for _, c := range req.Commands {
cmds = append(cmds, game.Command{
Bearing: c.Bearing,
Command: c.Command,
Duration: int(c.Duration)})
}
if err := s.world.Enqueue(id, cmds...); err != nil {
return nil, err
}
return &empty.Empty{}, nil
}

View file

@ -1,202 +0,0 @@
// +build integration
package internal
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"path"
"testing"
"github.com/google/uuid"
"github.com/mdiluz/rove/pkg/accounts"
"github.com/mdiluz/rove/pkg/atlas"
"github.com/mdiluz/rove/pkg/game"
"github.com/mdiluz/rove/pkg/rove"
"github.com/mdiluz/rove/pkg/vector"
"github.com/stretchr/testify/assert"
)
func TestHandleStatus(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/status", nil)
response := httptest.NewRecorder()
s := NewServer()
s.Initialise(true)
s.router.ServeHTTP(response, request)
assert.Equal(t, http.StatusOK, response.Code)
var status rove.StatusResponse
err := json.NewDecoder(response.Body).Decode(&status)
assert.NoError(t, err)
if status.Ready != true {
t.Errorf("got false for /status")
}
if len(status.Version) == 0 {
t.Errorf("got empty version info")
}
}
func TestHandleRegister(t *testing.T) {
data := rove.RegisterData{Name: uuid.New().String()}
b, err := json.Marshal(data)
if err != nil {
t.Error(err)
}
request, _ := http.NewRequest(http.MethodPost, "/register", bytes.NewReader(b))
response := httptest.NewRecorder()
s := NewServer()
s.Initialise(true)
s.router.ServeHTTP(response, request)
assert.Equal(t, http.StatusOK, response.Code)
var status rove.RegisterResponse
err = json.NewDecoder(response.Body).Decode(&status)
assert.NoError(t, err)
}
func TestHandleCommand(t *testing.T) {
name := uuid.New().String()
s := NewServer()
s.Initialise(false) // Leave the world empty with no obstacles
reg := accounts.RegisterInfo{Name: name}
acc, err := s.accountant.Register(context.Background(), &reg)
assert.NoError(t, err)
assert.NotNil(t, acc)
assert.True(t, acc.Success, acc.Error)
assert.NoError(t, err, "Error registering account")
// Spawn the rover rover for the account
_, inst, err := s.SpawnRoverForAccount(name)
assert.NoError(t, s.world.WarpRover(inst, vector.Vector{}))
attribs, err := s.world.RoverAttributes(inst)
assert.NoError(t, err, "Couldn't get rover position")
data := rove.CommandData{
Commands: []game.Command{
{
Command: game.CommandMove,
Bearing: "N",
Duration: 1,
},
},
}
b, err := json.Marshal(data)
assert.NoError(t, err, "Error marshalling data")
request, _ := http.NewRequest(http.MethodPost, path.Join("/", name, "/command"), bytes.NewReader(b))
response := httptest.NewRecorder()
s.router.ServeHTTP(response, request)
assert.Equal(t, http.StatusOK, response.Code)
var status rove.CommandResponse
err = json.NewDecoder(response.Body).Decode(&status)
assert.NoError(t, err)
attrib, err := s.world.RoverAttributes(inst)
assert.NoError(t, err, "Couldn't get rover attribs")
// Tick the command queues to progress the move command
s.world.EnqueueAllIncoming()
s.world.ExecuteCommandQueues()
attribs2, err := s.world.RoverAttributes(inst)
assert.NoError(t, err, "Couldn't get rover position")
attribs.Pos.Add(vector.Vector{X: 0.0, Y: attrib.Speed * 1}) // Should have moved north by the speed and duration
assert.Equal(t, attribs.Pos, attribs2.Pos, "Rover should have moved by bearing")
}
func TestHandleRadar(t *testing.T) {
name := uuid.New().String()
s := NewServer()
s.Initialise(false) // Spawn a clean world
reg := accounts.RegisterInfo{Name: name}
acc, err := s.accountant.Register(context.Background(), &reg)
assert.NoError(t, err)
assert.True(t, acc.Success, acc.Error)
assert.NoError(t, err, "Error registering account")
// Spawn the rover rover for the account
attrib, id, err := s.SpawnRoverForAccount(name)
assert.NoError(t, err)
// Warp this rover to 0,0
assert.NoError(t, s.world.WarpRover(id, vector.Vector{}))
// Explicity set a few nearby tiles
wallPos1 := vector.Vector{X: 0, Y: -1}
wallPos2 := vector.Vector{X: 1, Y: 1}
rockPos := vector.Vector{X: 1, Y: 3}
emptyPos := vector.Vector{X: -2, Y: -3}
assert.NoError(t, s.world.Atlas.SetTile(wallPos1, atlas.TileWall))
assert.NoError(t, s.world.Atlas.SetTile(wallPos2, atlas.TileWall))
assert.NoError(t, s.world.Atlas.SetTile(rockPos, atlas.TileRock))
assert.NoError(t, s.world.Atlas.SetTile(emptyPos, atlas.TileEmpty))
request, _ := http.NewRequest(http.MethodGet, path.Join("/", name, "/radar"), nil)
response := httptest.NewRecorder()
s.router.ServeHTTP(response, request)
assert.Equal(t, http.StatusOK, response.Code)
var status rove.RadarResponse
err = json.NewDecoder(response.Body).Decode(&status)
assert.NoError(t, err)
scope := attrib.Range*2 + 1
radarOrigin := vector.Vector{X: -attrib.Range, Y: -attrib.Range}
// Make sure the rover tile is correct
assert.Equal(t, atlas.TileRover, status.Tiles[len(status.Tiles)/2])
// Check our other tiles
wallPos1.Add(radarOrigin.Negated())
wallPos2.Add(radarOrigin.Negated())
rockPos.Add(radarOrigin.Negated())
emptyPos.Add(radarOrigin.Negated())
assert.Equal(t, atlas.TileWall, status.Tiles[wallPos1.X+wallPos1.Y*scope])
assert.Equal(t, atlas.TileWall, status.Tiles[wallPos2.X+wallPos2.Y*scope])
assert.Equal(t, atlas.TileRock, status.Tiles[rockPos.X+rockPos.Y*scope])
assert.Equal(t, atlas.TileEmpty, status.Tiles[emptyPos.X+emptyPos.Y*scope])
}
func TestHandleRover(t *testing.T) {
name := uuid.New().String()
s := NewServer()
s.Initialise(true)
reg := accounts.RegisterInfo{Name: name}
acc, err := s.accountant.Register(context.Background(), &reg)
assert.NoError(t, err)
assert.True(t, acc.Success, acc.Error)
// Spawn one rover for the account
attribs, _, err := s.SpawnRoverForAccount(name)
assert.NoError(t, err)
request, _ := http.NewRequest(http.MethodGet, path.Join("/", name, "/rover"), nil)
response := httptest.NewRecorder()
s.router.ServeHTTP(response, request)
assert.Equal(t, http.StatusOK, response.Code)
var status rove.RoverResponse
err = json.NewDecoder(response.Body).Decode(&status)
assert.NoError(t, err)
if attribs != status.Attributes {
t.Errorf("Missmatched attributes: %+v, !=%+v", attribs, status.Attributes)
}
}

View file

@ -2,20 +2,17 @@ package internal
import (
"context"
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"os"
"sync"
"time"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/mdiluz/rove/pkg/accounts"
"github.com/mdiluz/rove/pkg/game"
"github.com/mdiluz/rove/pkg/persistence"
"github.com/mdiluz/rove/pkg/rove"
"github.com/robfig/cron"
"google.golang.org/grpc"
)
@ -40,10 +37,9 @@ type Server struct {
accountant accounts.AccountantClient
clientConn *grpc.ClientConn
// HTTP server
listener net.Listener
server *http.Server
router *mux.Router
// gRPC server
netListener net.Listener
grpcServ *grpc.Server
// Config settings
address string
@ -85,13 +81,10 @@ func OptionTick(minutes int) ServerOption {
// NewServer sets up a new server
func NewServer(opts ...ServerOption) *Server {
router := mux.NewRouter().StrictSlash(true)
// Set up the default server
s := &Server{
address: "",
persistence: EphemeralData,
router: router,
schedule: cron.New(),
}
@ -100,9 +93,6 @@ func NewServer(opts ...ServerOption) *Server {
o(s)
}
// Set up the server object
s.server = &http.Server{Addr: s.address, Handler: s.router}
// Start small, we can grow the world later
s.world = game.NewWorld(4, 8)
@ -133,18 +123,14 @@ func (s *Server) Initialise(fillWorld bool) (err error) {
return err
}
// Set up the handlers
for _, route := range Routes {
s.router.HandleFunc(route.path, s.wrapHandler(route.method, route.handler))
// Set up the RPC server and register
s.netListener, err = net.Listen("tcp", s.address)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s.grpcServ = grpc.NewServer()
rove.RegisterRoverServerServer(s.grpcServ, s)
// Start the listen
log.Printf("Listening on %s\n", s.server.Addr)
if s.listener, err = net.Listen("tcp", s.server.Addr); err != nil {
return err
}
s.address = s.listener.Addr().String()
return nil
}
@ -178,9 +164,10 @@ func (s *Server) Run() {
log.Printf("First server tick scheduled for %s\n", s.schedule.Entries()[0].Next.Format("15:04:05"))
}
// Serve the http requests
if err := s.server.Serve(s.listener); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
// Serve the RPC server
log.Printf("Serving rove on %s\n", s.address)
if err := s.grpcServ.Serve(s.netListener); err != nil && err != grpc.ErrServerStopped {
log.Fatalf("failed to serve gRPC: %s", err)
}
}
@ -189,12 +176,8 @@ func (s *Server) Stop() error {
// Stop the cron
s.schedule.Stop()
// Try and shut down the http server
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := s.server.Shutdown(ctx); err != nil {
return err
}
// Stop the gRPC
s.grpcServ.Stop()
// Close the accountant connection
if err := s.clientConn.Close(); err != nil {
@ -206,7 +189,7 @@ func (s *Server) Stop() error {
// Close waits until the server is finished and closes up shop
func (s *Server) Close() error {
// Wait until the server has shut down
// Wait until the world has shut down
s.sync.Wait()
// Save and return
@ -253,40 +236,6 @@ type BadRequestError struct {
Error string `json:"error"`
}
// wrapHandler wraps a request handler in http checks
func (s *Server) wrapHandler(method string, handler Handler) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// Log the request
log.Printf("%s\t%s\n", r.Method, r.RequestURI)
vars := mux.Vars(r)
// Verify the method, call the handler, and encode the return
if r.Method != method {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
val, err := handler(s, vars, r.Body)
if err != nil {
log.Printf("Failed to handle http request: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
} else if _, ok := val.(BadRequestError); ok {
w.WriteHeader(http.StatusBadRequest)
}
if err := json.NewEncoder(w).Encode(val); err != nil {
log.Printf("Failed to encode reply to json: %s", err)
w.WriteHeader(http.StatusInternalServerError)
} else {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
}
}
}
// SpawnRoverForAccount spawns the rover rover for an account
func (s *Server) SpawnRoverForAccount(account string) (game.RoverAttributes, uuid.UUID, error) {
if inst, err := s.world.SpawnRover(); err != nil {
@ -297,9 +246,9 @@ func (s *Server) SpawnRoverForAccount(account string) (game.RoverAttributes, uui
} else {
keyval := accounts.DataKeyValue{Account: account, Key: "rover", Value: inst.String()}
resp, err := s.accountant.AssignValue(context.Background(), &keyval)
if err != nil || !resp.Success {
log.Printf("Failed to assign rover to account, %s, %s", err, resp.Error)
_, err := s.accountant.AssignValue(context.Background(), &keyval)
if err != nil {
log.Printf("Failed to assign rover to account, %s", err)
// Try and clear up the rover
if err := s.world.DestroyRover(inst); err != nil {

View file

@ -7,10 +7,14 @@ import (
"io/ioutil"
"os"
"path"
"time"
"github.com/golang/protobuf/ptypes/empty"
"github.com/mdiluz/rove/pkg/game"
"github.com/mdiluz/rove/pkg/rove"
"github.com/mdiluz/rove/pkg/version"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
var USAGE = ""
@ -90,7 +94,13 @@ func InnerMain(command string) error {
}
// Set up the server
var server = rove.Server(config.Host)
clientConn, err := grpc.Dial(config.Host, grpc.WithInsecure())
if err != nil {
return err
}
var client = rove.NewRoverServerClient(clientConn)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Grab the account
var account = config.Accounts[config.Host]
@ -98,7 +108,7 @@ func InnerMain(command string) error {
// Handle all the commands
switch command {
case "status":
response, err := server.Status()
response, err := client.Status(ctx, &empty.Empty{})
switch {
case err != nil:
return err
@ -114,10 +124,10 @@ func InnerMain(command string) error {
if len(*name) == 0 {
return fmt.Errorf("must set name with -name")
}
d := rove.RegisterData{
d := rove.RegisterRequest{
Name: *name,
}
_, err := server.Register(d)
_, err := client.Register(ctx, &d)
switch {
case err != nil:
return err
@ -128,11 +138,12 @@ func InnerMain(command string) error {
}
case "move":
d := rove.CommandData{
Commands: []game.Command{
d := rove.CommandsRequest{
Account: account,
Commands: []*rove.Command{
{
Command: game.CommandMove,
Duration: *duration,
Duration: int32(*duration),
Bearing: *bearing,
},
},
@ -142,7 +153,7 @@ func InnerMain(command string) error {
return err
}
_, err := server.Command(account, d)
_, err := client.Commands(ctx, &d)
switch {
case err != nil:
return err
@ -152,11 +163,12 @@ func InnerMain(command string) error {
}
case "radar":
dat := rove.RadarRequest{Account: account}
if err := verifyId(account); err != nil {
return err
}
response, err := server.Radar(account)
response, err := client.Radar(ctx, &dat)
switch {
case err != nil:
return err
@ -167,17 +179,18 @@ func InnerMain(command string) error {
}
case "rover":
req := rove.RoverRequest{Account: account}
if err := verifyId(account); err != nil {
return err
}
response, err := server.Rover(account)
response, err := client.Rover(ctx, &req)
switch {
case err != nil:
return err
default:
fmt.Printf("attributes: %+v\n", response.Attributes)
fmt.Printf("attributes: %+v\n", response)
}
case "config":
fmt.Printf("host: %s\taccount: %s\n", config.Host, account)

View file

@ -18,18 +18,8 @@ services:
- persistent-data:/mnt/rove-server:rw
command: [ ./rove-accountant ]
rove-docs:
build:
context: .
dockerfile: Dockerfile.docs
image: rove-docs:latest
ports:
- "80:80"
environment:
- PORT=80
rove-server:
depends_on: [ rove-accountant, rove-docs ]
depends_on: [ rove-accountant ]
build:
context: .
dockerfile: Dockerfile

1
go.mod
View file

@ -10,5 +10,6 @@ require (
github.com/robfig/cron v1.2.0
github.com/stretchr/testify v1.6.0
github.com/tjarratt/babble v0.0.0-20191209142150-eecdf8c2339d
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7
google.golang.org/grpc v1.29.1
)

View file

@ -67,9 +67,6 @@ func (m *RegisterInfo) GetName() string {
// RegisterResponse is the response information from registering an account
type RegisterResponse struct {
// The error value should only be populated if success is false
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -100,20 +97,6 @@ func (m *RegisterResponse) XXX_DiscardUnknown() {
var xxx_messageInfo_RegisterResponse proto.InternalMessageInfo
func (m *RegisterResponse) GetSuccess() bool {
if m != nil {
return m.Success
}
return false
}
func (m *RegisterResponse) GetError() string {
if m != nil {
return m.Error
}
return ""
}
// DataKeyValue represents a simple key value pair to assign to an account
type DataKeyValue struct {
// The account to assign the new key value pair to
@ -172,54 +155,37 @@ func (m *DataKeyValue) GetValue() string {
return ""
}
// Response is a simple response with success and error
type Response struct {
// error should only be populated if success is false
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
// DataKeyResponse is a simple response
type DataKeyResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
func (m *DataKeyResponse) Reset() { *m = DataKeyResponse{} }
func (m *DataKeyResponse) String() string { return proto.CompactTextString(m) }
func (*DataKeyResponse) ProtoMessage() {}
func (*DataKeyResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_e1e7723af4c007b7, []int{3}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
func (m *DataKeyResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DataKeyResponse.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
func (m *DataKeyResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_DataKeyResponse.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
func (m *DataKeyResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_DataKeyResponse.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
func (m *DataKeyResponse) XXX_Size() int {
return xxx_messageInfo_DataKeyResponse.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
func (m *DataKeyResponse) XXX_DiscardUnknown() {
xxx_messageInfo_DataKeyResponse.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetSuccess() bool {
if m != nil {
return m.Success
}
return false
}
func (m *Response) GetError() string {
if m != nil {
return m.Error
}
return ""
}
var xxx_messageInfo_DataKeyResponse proto.InternalMessageInfo
// DataKey describes a simple key value with an account, for fetching
type DataKey struct {
@ -273,9 +239,6 @@ func (m *DataKey) GetKey() string {
// DataResponse describes a data fetch response
type DataResponse struct {
// error should only be populated if success is false
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
// The value of the key
Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
@ -308,20 +271,6 @@ func (m *DataResponse) XXX_DiscardUnknown() {
var xxx_messageInfo_DataResponse proto.InternalMessageInfo
func (m *DataResponse) GetSuccess() bool {
if m != nil {
return m.Success
}
return false
}
func (m *DataResponse) GetError() string {
if m != nil {
return m.Error
}
return ""
}
func (m *DataResponse) GetValue() string {
if m != nil {
return m.Value
@ -333,7 +282,7 @@ func init() {
proto.RegisterType((*RegisterInfo)(nil), "accounts.RegisterInfo")
proto.RegisterType((*RegisterResponse)(nil), "accounts.RegisterResponse")
proto.RegisterType((*DataKeyValue)(nil), "accounts.DataKeyValue")
proto.RegisterType((*Response)(nil), "accounts.Response")
proto.RegisterType((*DataKeyResponse)(nil), "accounts.DataKeyResponse")
proto.RegisterType((*DataKey)(nil), "accounts.DataKey")
proto.RegisterType((*DataResponse)(nil), "accounts.DataResponse")
}
@ -343,26 +292,25 @@ func init() {
}
var fileDescriptor_e1e7723af4c007b7 = []byte{
// 298 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x92, 0x4f, 0x4b, 0xc3, 0x40,
0x10, 0xc5, 0x8d, 0x55, 0x1b, 0xc7, 0x22, 0x75, 0x11, 0x09, 0x39, 0xc9, 0x8a, 0xe0, 0x29, 0x01,
0x45, 0x04, 0xbd, 0xd8, 0x22, 0x88, 0x78, 0x91, 0x20, 0x1e, 0xbc, 0x6d, 0xe3, 0x18, 0x43, 0x9b,
0xdd, 0xb0, 0x7f, 0x0a, 0xf5, 0xf3, 0xf9, 0xc1, 0x24, 0xbb, 0xd9, 0x1a, 0xac, 0x17, 0x7b, 0xdb,
0x17, 0xe6, 0xf7, 0xe6, 0xcd, 0x4c, 0x60, 0x9f, 0xe5, 0xb9, 0x30, 0x5c, 0xab, 0xa4, 0x96, 0x42,
0x0b, 0x12, 0x7a, 0x4d, 0x29, 0x0c, 0x32, 0x2c, 0x4a, 0xa5, 0x51, 0x3e, 0xf0, 0x77, 0x41, 0x08,
0x6c, 0x71, 0x56, 0x61, 0x14, 0x1c, 0x07, 0x67, 0xbb, 0x99, 0x7d, 0xd3, 0x31, 0x0c, 0x7d, 0x4d,
0x86, 0xaa, 0x16, 0x5c, 0x21, 0x89, 0xa0, 0xaf, 0x4c, 0x9e, 0xa3, 0x52, 0xb6, 0x34, 0xcc, 0xbc,
0x24, 0x87, 0xb0, 0x8d, 0x52, 0x0a, 0x19, 0x6d, 0x5a, 0x0b, 0x27, 0xe8, 0x13, 0x0c, 0xee, 0x98,
0x66, 0x8f, 0xb8, 0x78, 0x61, 0x33, 0x63, 0xf9, 0x36, 0x43, 0xdb, 0xca, 0x4b, 0x32, 0x84, 0xde,
0x14, 0x17, 0x2d, 0xdd, 0x3c, 0x1b, 0xc7, 0x79, 0x03, 0x45, 0x3d, 0xe7, 0x68, 0x05, 0xbd, 0x86,
0x70, 0xed, 0x34, 0x97, 0xd0, 0x6f, 0xd3, 0xfc, 0x27, 0x08, 0x7d, 0x76, 0x43, 0xac, 0xdb, 0xf6,
0xef, 0x41, 0xce, 0xbf, 0x02, 0x80, 0x91, 0xeb, 0xc9, 0xb8, 0x26, 0xb7, 0xcd, 0x5c, 0x6e, 0xdb,
0xe4, 0x28, 0x59, 0x1e, 0xae, 0x7b, 0xa5, 0x38, 0x5e, 0xfd, 0xee, 0x43, 0xd1, 0x0d, 0x72, 0x03,
0x7b, 0x23, 0xa5, 0xca, 0x82, 0xbb, 0x55, 0x77, 0x4c, 0xba, 0x27, 0x88, 0x49, 0xd7, 0x64, 0x09,
0x5f, 0x41, 0x78, 0x8f, 0xda, 0x91, 0x07, 0x2b, 0x64, 0xfc, 0xcb, 0xec, 0x07, 0x1c, 0x9f, 0xbe,
0x9e, 0x14, 0xa5, 0xfe, 0x30, 0x93, 0x24, 0x17, 0x55, 0x5a, 0xbd, 0x95, 0x33, 0xf3, 0x99, 0x4a,
0x31, 0xc7, 0xb4, 0x9e, 0x16, 0xa9, 0xa7, 0x26, 0x3b, 0xf6, 0x0f, 0xbc, 0xf8, 0x0e, 0x00, 0x00,
0xff, 0xff, 0xef, 0x53, 0xc6, 0xba, 0x93, 0x02, 0x00, 0x00,
// 276 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0x41, 0x4b, 0xc3, 0x40,
0x10, 0x85, 0x8d, 0x55, 0x1b, 0xc7, 0xa2, 0xed, 0x20, 0x12, 0x73, 0x92, 0x55, 0xc1, 0x53, 0x02,
0x8a, 0x78, 0xb5, 0x45, 0x10, 0xf1, 0x22, 0x39, 0x78, 0xf0, 0xb6, 0x8d, 0x63, 0x0c, 0x6d, 0x76,
0x43, 0x76, 0x53, 0xa8, 0x7f, 0xd1, 0x3f, 0x25, 0xc9, 0x66, 0x63, 0x30, 0x5e, 0x7a, 0x9b, 0x79,
0xcc, 0xfb, 0x66, 0xf6, 0x2d, 0x1c, 0xf2, 0x38, 0x96, 0xa5, 0xd0, 0x2a, 0xc8, 0x0b, 0xa9, 0x25,
0xba, 0xb6, 0x67, 0x0c, 0x46, 0x11, 0x25, 0xa9, 0xd2, 0x54, 0x3c, 0x89, 0x0f, 0x89, 0x08, 0x3b,
0x82, 0x67, 0xe4, 0x39, 0x67, 0xce, 0xd5, 0x7e, 0x54, 0xd7, 0x0c, 0x61, 0x6c, 0x67, 0x22, 0x52,
0xb9, 0x14, 0x8a, 0xd8, 0x0b, 0x8c, 0x1e, 0xb8, 0xe6, 0xcf, 0xb4, 0x7e, 0xe5, 0xcb, 0x92, 0xd0,
0x83, 0x61, 0xc3, 0x6c, 0xac, 0xb6, 0xc5, 0x31, 0x0c, 0x16, 0xb4, 0xf6, 0xb6, 0x6b, 0xb5, 0x2a,
0xf1, 0x18, 0x76, 0x57, 0x95, 0xc9, 0x1b, 0xd4, 0x9a, 0x69, 0xd8, 0x04, 0x8e, 0x1a, 0x62, 0xbb,
0xe4, 0x16, 0x86, 0x8d, 0xb4, 0x09, 0x9f, 0x5d, 0x98, 0xdb, 0x2c, 0xe6, 0xff, 0x7d, 0xd7, 0xdf,
0x0e, 0xc0, 0xd4, 0x30, 0xb8, 0xd0, 0x78, 0x0f, 0xae, 0x7d, 0x24, 0x9e, 0x04, 0x6d, 0x5e, 0xdd,
0x70, 0x7c, 0xbf, 0xaf, 0xb7, 0xb7, 0x6e, 0xe1, 0x0c, 0x0e, 0xa6, 0x4a, 0xa5, 0x89, 0x30, 0x89,
0x74, 0x20, 0xdd, 0xa4, 0xfc, 0xd3, 0x9e, 0xde, 0x61, 0xdc, 0x81, 0xfb, 0x48, 0xda, 0x00, 0x26,
0xbd, 0x41, 0xff, 0x0f, 0xf3, 0xd7, 0x38, 0xbb, 0x7c, 0x3b, 0x4f, 0x52, 0xfd, 0x59, 0xce, 0x83,
0x58, 0x66, 0x61, 0xf6, 0x9e, 0x2e, 0xcb, 0xaf, 0xb0, 0x90, 0x2b, 0x0a, 0xf3, 0x45, 0x12, 0x5a,
0xd7, 0x7c, 0xaf, 0xfe, 0xff, 0x9b, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x22, 0x35, 0x31, 0x6f,
0x11, 0x02, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -381,7 +329,7 @@ type AccountantClient interface {
// It will return an error if the account already exists
Register(ctx context.Context, in *RegisterInfo, opts ...grpc.CallOption) (*RegisterResponse, error)
// AssignValue assigns a key-value pair to an account, or overwrites an existing key
AssignValue(ctx context.Context, in *DataKeyValue, opts ...grpc.CallOption) (*Response, error)
AssignValue(ctx context.Context, in *DataKeyValue, opts ...grpc.CallOption) (*DataKeyResponse, error)
// GetValue will get the value for a key for an account
GetValue(ctx context.Context, in *DataKey, opts ...grpc.CallOption) (*DataResponse, error)
}
@ -403,8 +351,8 @@ func (c *accountantClient) Register(ctx context.Context, in *RegisterInfo, opts
return out, nil
}
func (c *accountantClient) AssignValue(ctx context.Context, in *DataKeyValue, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
func (c *accountantClient) AssignValue(ctx context.Context, in *DataKeyValue, opts ...grpc.CallOption) (*DataKeyResponse, error) {
out := new(DataKeyResponse)
err := c.cc.Invoke(ctx, "/accounts.Accountant/AssignValue", in, out, opts...)
if err != nil {
return nil, err
@ -427,7 +375,7 @@ type AccountantServer interface {
// It will return an error if the account already exists
Register(context.Context, *RegisterInfo) (*RegisterResponse, error)
// AssignValue assigns a key-value pair to an account, or overwrites an existing key
AssignValue(context.Context, *DataKeyValue) (*Response, error)
AssignValue(context.Context, *DataKeyValue) (*DataKeyResponse, error)
// GetValue will get the value for a key for an account
GetValue(context.Context, *DataKey) (*DataResponse, error)
}
@ -439,7 +387,7 @@ type UnimplementedAccountantServer struct {
func (*UnimplementedAccountantServer) Register(ctx context.Context, req *RegisterInfo) (*RegisterResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Register not implemented")
}
func (*UnimplementedAccountantServer) AssignValue(ctx context.Context, req *DataKeyValue) (*Response, error) {
func (*UnimplementedAccountantServer) AssignValue(ctx context.Context, req *DataKeyValue) (*DataKeyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AssignValue not implemented")
}
func (*UnimplementedAccountantServer) GetValue(ctx context.Context, req *DataKey) (*DataResponse, error) {

View file

@ -10,7 +10,7 @@ service Accountant {
rpc Register(RegisterInfo) returns (RegisterResponse) {}
// AssignValue assigns a key-value pair to an account, or overwrites an existing key
rpc AssignValue(DataKeyValue) returns (Response) {}
rpc AssignValue(DataKeyValue) returns (DataKeyResponse) {}
// GetValue will get the value for a key for an account
rpc GetValue(DataKey) returns (DataResponse) {}
@ -23,11 +23,7 @@ message RegisterInfo {
}
// RegisterResponse is the response information from registering an account
message RegisterResponse {
// The error value should only be populated if success is false
bool success = 1;
string error = 2;
}
message RegisterResponse {}
// DataKeyValue represents a simple key value pair to assign to an account
message DataKeyValue {
@ -39,12 +35,8 @@ message DataKeyValue {
string value = 3;
}
// Response is a simple response with success and error
message Response {
// error should only be populated if success is false
bool success = 1;
string error = 2;
}
// DataKeyResponse is a simple response
message DataKeyResponse {}
// DataKey describes a simple key value with an account, for fetching
message DataKey {
@ -57,10 +49,6 @@ message DataKey {
// DataResponse describes a data fetch response
message DataResponse {
// error should only be populated if success is false
bool success = 1;
string error = 2;
// The value of the key
string value = 3;
}

View file

@ -12,7 +12,7 @@ import (
// Chunk represents a fixed square grid of tiles
type Chunk struct {
// Tiles represents the tiles within the chunk
Tiles []Tile `json:"tiles"`
Tiles []byte `json:"tiles"`
}
// Atlas represents a grid of Chunks
@ -43,7 +43,7 @@ func NewAtlas(size, chunkSize int) Atlas {
// Initialise all the chunks
for i := range a.Chunks {
a.Chunks[i] = Chunk{
Tiles: make([]Tile, chunkSize*chunkSize),
Tiles: make([]byte, chunkSize*chunkSize),
}
}
@ -90,7 +90,7 @@ func (a *Atlas) SpawnWalls() error {
}
// SetTile sets an individual tile's kind
func (a *Atlas) SetTile(v vector.Vector, tile Tile) error {
func (a *Atlas) SetTile(v vector.Vector, tile byte) error {
chunk := a.toChunk(v)
if chunk >= len(a.Chunks) {
return fmt.Errorf("location outside of allocated atlas")
@ -106,7 +106,7 @@ func (a *Atlas) SetTile(v vector.Vector, tile Tile) error {
}
// GetTile will return an individual tile
func (a *Atlas) GetTile(v vector.Vector) (Tile, error) {
func (a *Atlas) GetTile(v vector.Vector) (byte, error) {
chunk := a.toChunk(v)
if chunk >= len(a.Chunks) {
return 0, fmt.Errorf("location outside of allocated atlas")

View file

@ -81,13 +81,13 @@ func TestAtlas_GetSetTile(t *testing.T) {
assert.NoError(t, a.SetTile(vector.Vector{X: 0, Y: 0}, 1))
tile, err := a.GetTile(vector.Vector{X: 0, Y: 0})
assert.NoError(t, err)
assert.Equal(t, Tile(1), tile)
assert.Equal(t, byte(1), tile)
// Set another tile to 1 and test it
assert.NoError(t, a.SetTile(vector.Vector{X: 5, Y: -2}, 2))
tile, err = a.GetTile(vector.Vector{X: 5, Y: -2})
assert.NoError(t, err)
assert.Equal(t, Tile(2), tile)
assert.Equal(t, byte(2), tile)
}
func TestAtlas_Grown(t *testing.T) {
@ -108,15 +108,15 @@ func TestAtlas_Grown(t *testing.T) {
tile, err := a.GetTile(vector.Vector{X: 0, Y: 0})
assert.NoError(t, err)
assert.Equal(t, Tile(1), tile)
assert.Equal(t, byte(1), tile)
tile, err = a.GetTile(vector.Vector{X: -1, Y: -1})
assert.NoError(t, err)
assert.Equal(t, Tile(2), tile)
assert.Equal(t, byte(2), tile)
tile, err = a.GetTile(vector.Vector{X: 1, Y: -2})
assert.NoError(t, err)
assert.Equal(t, Tile(3), tile)
assert.Equal(t, byte(3), tile)
// Grow it again even bigger
err = a.Grow(10)
@ -125,15 +125,15 @@ func TestAtlas_Grown(t *testing.T) {
tile, err = a.GetTile(vector.Vector{X: 0, Y: 0})
assert.NoError(t, err)
assert.Equal(t, Tile(1), tile)
assert.Equal(t, byte(1), tile)
tile, err = a.GetTile(vector.Vector{X: -1, Y: -1})
assert.NoError(t, err)
assert.Equal(t, Tile(2), tile)
assert.Equal(t, byte(2), tile)
tile, err = a.GetTile(vector.Vector{X: 1, Y: -2})
assert.NoError(t, err)
assert.Equal(t, Tile(3), tile)
assert.Equal(t, byte(3), tile)
}
func TestAtlas_SpawnWorld(t *testing.T) {

View file

@ -1,12 +1,9 @@
package atlas
// Tile represents the type of a tile on the map
type Tile byte
const (
TileEmpty = Tile(0)
TileRover = Tile(1)
TileEmpty = byte(0)
TileRover = byte(1)
TileWall = Tile(2)
TileRock = Tile(3)
TileWall = byte(2)
TileRock = byte(3)
)

View file

@ -22,7 +22,7 @@ func TestCommand_Move(t *testing.T) {
err = world.WarpRover(a, pos)
assert.NoError(t, err, "Failed to set position for rover")
duration := 1
var duration = 1
// Try the move command
moveCommand := Command{Command: CommandMove, Bearing: "N", Duration: duration}
assert.NoError(t, world.Enqueue(a, moveCommand), "Failed to execute move command")
@ -33,6 +33,6 @@ func TestCommand_Move(t *testing.T) {
newatributes, err := world.RoverAttributes(a)
assert.NoError(t, err, "Failed to set position for rover")
pos.Add(vector.Vector{X: 0.0, Y: duration * attribs.Speed}) // We should have moved duration*speed north
pos.Add(vector.Vector{X: 0.0, Y: int(duration) * int(attribs.Speed)}) // We should have moved duration*speed north
assert.Equal(t, pos, newatributes.Pos, "Failed to correctly set position for rover")
}

View file

@ -225,7 +225,7 @@ func (w *World) MoveRover(id uuid.UUID, b bearing.Bearing) (RoverAttributes, err
}
// RadarFromRover can be used to query what a rover can currently see
func (w *World) RadarFromRover(id uuid.UUID) ([]atlas.Tile, error) {
func (w *World) RadarFromRover(id uuid.UUID) ([]byte, error) {
w.worldMutex.RLock()
defer w.worldMutex.RUnlock()
@ -256,7 +256,7 @@ func (w *World) RadarFromRover(id uuid.UUID) ([]atlas.Tile, error) {
}
// Gather up all tiles within the range
var radar = make([]atlas.Tile, radarSpan*radarSpan)
var radar = make([]byte, radarSpan*radarSpan)
for j := scanMin.Y; j <= scanMax.Y; j++ {
for i := scanMin.X; i <= scanMax.X; i++ {
q := vector.Vector{X: i, Y: j}
@ -381,7 +381,7 @@ func (w *World) ExecuteCommand(c *Command, rover uuid.UUID) (finished bool, err
}
// PrintTiles simply prints the input tiles directly for debug
func PrintTiles(tiles []atlas.Tile) {
func PrintTiles(tiles []byte) {
num := int(math.Sqrt(float64(len(tiles))))
for j := num - 1; j >= 0; j-- {
for i := 0; i < num; i++ {

View file

@ -1,94 +0,0 @@
package rove
import (
"path"
"github.com/mdiluz/rove/pkg/atlas"
"github.com/mdiluz/rove/pkg/game"
)
// ==============================
// API: /status method: GET
// Status queries the status of the server
func (s Server) Status() (r StatusResponse, err error) {
s.Get("status", &r)
return
}
// StatusResponse is a struct that contains information on the status of the server
type StatusResponse struct {
Ready bool `json:"ready"`
Version string `json:"version"`
Tick int `json:"tick"`
NextTick string `json:"nexttick,omitempty"`
}
// ==============================
// API: /register method: POST
// Register registers a user by name
func (s Server) Register(d RegisterData) (r RegisterResponse, err error) {
err = s.Post("register", d, &r)
return
}
// RegisterData describes the data to send when registering
type RegisterData struct {
Name string `json:"name"`
}
// RegisterResponse describes the response to a register request
type RegisterResponse struct {
// Placeholder for future information
}
// ==============================
// API: /{account}/command method: POST
// Command issues a set of commands from the user
func (s Server) Command(account string, d CommandData) (r CommandResponse, err error) {
err = s.Post(path.Join(account, "command"), d, &r)
return
}
// CommandData is a set of commands to execute in order
type CommandData struct {
Commands []game.Command `json:"commands"`
}
// CommandResponse is the response to be sent back
type CommandResponse struct {
// Placeholder for future information
}
// ================
// API: /{account}/radar method: GET
// Radar queries the current radar for the user
func (s Server) Radar(account string) (r RadarResponse, err error) {
err = s.Get(path.Join(account, "radar"), &r)
return
}
// RadarResponse describes the response to a /radar call
type RadarResponse struct {
// The set of positions for nearby non-empty tiles
Range int `json:"range"`
Tiles []atlas.Tile `json:"tiles"`
}
// ================
// API: /{account}/rover method: GET
// Rover queries the current state of the rover
func (s Server) Rover(account string) (r RoverResponse, err error) {
err = s.Get(path.Join(account, "rover"), &r)
return
}
// RoverResponse includes information about the rover in question
type RoverResponse struct {
// The current position of this rover
Attributes game.RoverAttributes `json:"attributes"`
}

View file

@ -1,73 +0,0 @@
package rove
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
// Server is a simple wrapper to a server path
type Server string
// Get performs a Get request
func (s Server) Get(path string, out interface{}) error {
u := url.URL{
Scheme: "http",
Host: string(s),
Path: path,
}
if resp, err := http.Get(u.String()); err != nil {
return err
} else if resp.StatusCode != http.StatusOK {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body to code %d", resp.StatusCode)
}
return fmt.Errorf("http returned status %d: %s", resp.StatusCode, string(body))
} else {
return json.NewDecoder(resp.Body).Decode(out)
}
}
// Post performs a Post request
func (s Server) Post(path string, in, out interface{}) error {
u := url.URL{
Scheme: "http",
Host: string(s),
Path: path,
}
client := &http.Client{}
// Marshal the input
marshalled, err := json.Marshal(in)
if err != nil {
return err
}
// Set up the request
req, err := http.NewRequest("POST", u.String(), bytes.NewReader(marshalled))
if err != nil {
return err
}
// Do the POST
req.Header.Set("Content-Type", "application/json")
if resp, err := client.Do(req); err != nil {
return err
} else if resp.StatusCode != http.StatusOK {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body to code %d", resp.StatusCode)
}
return fmt.Errorf("http returned status %d: %s", resp.StatusCode, string(body))
} else {
return json.NewDecoder(resp.Body).Decode(out)
}
}

821
pkg/rove/rove.pb.go Normal file
View file

@ -0,0 +1,821 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: rove.proto
package rove
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
empty "github.com/golang/protobuf/ptypes/empty"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Command struct {
// The command to execute, currently only accepts move, which requires a bearing and a duration.
Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"`
Bearing string `protobuf:"bytes,2,opt,name=bearing,proto3" json:"bearing,omitempty"`
Duration int32 `protobuf:"varint,3,opt,name=duration,proto3" json:"duration,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Command) Reset() { *m = Command{} }
func (m *Command) String() string { return proto.CompactTextString(m) }
func (*Command) ProtoMessage() {}
func (*Command) Descriptor() ([]byte, []int) {
return fileDescriptor_cea399972d1e9fa7, []int{0}
}
func (m *Command) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Command.Unmarshal(m, b)
}
func (m *Command) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Command.Marshal(b, m, deterministic)
}
func (m *Command) XXX_Merge(src proto.Message) {
xxx_messageInfo_Command.Merge(m, src)
}
func (m *Command) XXX_Size() int {
return xxx_messageInfo_Command.Size(m)
}
func (m *Command) XXX_DiscardUnknown() {
xxx_messageInfo_Command.DiscardUnknown(m)
}
var xxx_messageInfo_Command proto.InternalMessageInfo
func (m *Command) GetCommand() string {
if m != nil {
return m.Command
}
return ""
}
func (m *Command) GetBearing() string {
if m != nil {
return m.Bearing
}
return ""
}
func (m *Command) GetDuration() int32 {
if m != nil {
return m.Duration
}
return 0
}
type CommandsRequest struct {
Account string `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"`
Commands []*Command `protobuf:"bytes,2,rep,name=commands,proto3" json:"commands,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CommandsRequest) Reset() { *m = CommandsRequest{} }
func (m *CommandsRequest) String() string { return proto.CompactTextString(m) }
func (*CommandsRequest) ProtoMessage() {}
func (*CommandsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_cea399972d1e9fa7, []int{1}
}
func (m *CommandsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CommandsRequest.Unmarshal(m, b)
}
func (m *CommandsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CommandsRequest.Marshal(b, m, deterministic)
}
func (m *CommandsRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_CommandsRequest.Merge(m, src)
}
func (m *CommandsRequest) XXX_Size() int {
return xxx_messageInfo_CommandsRequest.Size(m)
}
func (m *CommandsRequest) XXX_DiscardUnknown() {
xxx_messageInfo_CommandsRequest.DiscardUnknown(m)
}
var xxx_messageInfo_CommandsRequest proto.InternalMessageInfo
func (m *CommandsRequest) GetAccount() string {
if m != nil {
return m.Account
}
return ""
}
func (m *CommandsRequest) GetCommands() []*Command {
if m != nil {
return m.Commands
}
return nil
}
type Error struct {
// An explanation for the HTTP error returned
Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Error) Reset() { *m = Error{} }
func (m *Error) String() string { return proto.CompactTextString(m) }
func (*Error) ProtoMessage() {}
func (*Error) Descriptor() ([]byte, []int) {
return fileDescriptor_cea399972d1e9fa7, []int{2}
}
func (m *Error) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Error.Unmarshal(m, b)
}
func (m *Error) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Error.Marshal(b, m, deterministic)
}
func (m *Error) XXX_Merge(src proto.Message) {
xxx_messageInfo_Error.Merge(m, src)
}
func (m *Error) XXX_Size() int {
return xxx_messageInfo_Error.Size(m)
}
func (m *Error) XXX_DiscardUnknown() {
xxx_messageInfo_Error.DiscardUnknown(m)
}
var xxx_messageInfo_Error proto.InternalMessageInfo
func (m *Error) GetError() string {
if m != nil {
return m.Error
}
return ""
}
type RadarRequest struct {
Account string `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RadarRequest) Reset() { *m = RadarRequest{} }
func (m *RadarRequest) String() string { return proto.CompactTextString(m) }
func (*RadarRequest) ProtoMessage() {}
func (*RadarRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_cea399972d1e9fa7, []int{3}
}
func (m *RadarRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RadarRequest.Unmarshal(m, b)
}
func (m *RadarRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RadarRequest.Marshal(b, m, deterministic)
}
func (m *RadarRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_RadarRequest.Merge(m, src)
}
func (m *RadarRequest) XXX_Size() int {
return xxx_messageInfo_RadarRequest.Size(m)
}
func (m *RadarRequest) XXX_DiscardUnknown() {
xxx_messageInfo_RadarRequest.DiscardUnknown(m)
}
var xxx_messageInfo_RadarRequest proto.InternalMessageInfo
func (m *RadarRequest) GetAccount() string {
if m != nil {
return m.Account
}
return ""
}
type RadarResponse struct {
// The range in tiles from the rover of the radar data
Range int32 `protobuf:"varint,1,opt,name=range,proto3" json:"range,omitempty"`
// A 1D array representing range*2 + 1 squared set of tiles, origin bottom left and in row->column order
Tiles []byte `protobuf:"bytes,2,opt,name=tiles,proto3" json:"tiles,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RadarResponse) Reset() { *m = RadarResponse{} }
func (m *RadarResponse) String() string { return proto.CompactTextString(m) }
func (*RadarResponse) ProtoMessage() {}
func (*RadarResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_cea399972d1e9fa7, []int{4}
}
func (m *RadarResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RadarResponse.Unmarshal(m, b)
}
func (m *RadarResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RadarResponse.Marshal(b, m, deterministic)
}
func (m *RadarResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_RadarResponse.Merge(m, src)
}
func (m *RadarResponse) XXX_Size() int {
return xxx_messageInfo_RadarResponse.Size(m)
}
func (m *RadarResponse) XXX_DiscardUnknown() {
xxx_messageInfo_RadarResponse.DiscardUnknown(m)
}
var xxx_messageInfo_RadarResponse proto.InternalMessageInfo
func (m *RadarResponse) GetRange() int32 {
if m != nil {
return m.Range
}
return 0
}
func (m *RadarResponse) GetTiles() []byte {
if m != nil {
return m.Tiles
}
return nil
}
type RegisterRequest struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RegisterRequest) Reset() { *m = RegisterRequest{} }
func (m *RegisterRequest) String() string { return proto.CompactTextString(m) }
func (*RegisterRequest) ProtoMessage() {}
func (*RegisterRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_cea399972d1e9fa7, []int{5}
}
func (m *RegisterRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RegisterRequest.Unmarshal(m, b)
}
func (m *RegisterRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RegisterRequest.Marshal(b, m, deterministic)
}
func (m *RegisterRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_RegisterRequest.Merge(m, src)
}
func (m *RegisterRequest) XXX_Size() int {
return xxx_messageInfo_RegisterRequest.Size(m)
}
func (m *RegisterRequest) XXX_DiscardUnknown() {
xxx_messageInfo_RegisterRequest.DiscardUnknown(m)
}
var xxx_messageInfo_RegisterRequest proto.InternalMessageInfo
func (m *RegisterRequest) GetName() string {
if m != nil {
return m.Name
}
return ""
}
type RoverRequest struct {
Account string `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RoverRequest) Reset() { *m = RoverRequest{} }
func (m *RoverRequest) String() string { return proto.CompactTextString(m) }
func (*RoverRequest) ProtoMessage() {}
func (*RoverRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_cea399972d1e9fa7, []int{6}
}
func (m *RoverRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RoverRequest.Unmarshal(m, b)
}
func (m *RoverRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RoverRequest.Marshal(b, m, deterministic)
}
func (m *RoverRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_RoverRequest.Merge(m, src)
}
func (m *RoverRequest) XXX_Size() int {
return xxx_messageInfo_RoverRequest.Size(m)
}
func (m *RoverRequest) XXX_DiscardUnknown() {
xxx_messageInfo_RoverRequest.DiscardUnknown(m)
}
var xxx_messageInfo_RoverRequest proto.InternalMessageInfo
func (m *RoverRequest) GetAccount() string {
if m != nil {
return m.Account
}
return ""
}
type RoverResponse struct {
// The name of the rover
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// Position of the rover in world coordinates
Position *Vector `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"`
// The range of this rover's radar
Range int32 `protobuf:"varint,3,opt,name=range,proto3" json:"range,omitempty"`
// The speed the rover can move per tick
Speed int32 `protobuf:"varint,4,opt,name=speed,proto3" json:"speed,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RoverResponse) Reset() { *m = RoverResponse{} }
func (m *RoverResponse) String() string { return proto.CompactTextString(m) }
func (*RoverResponse) ProtoMessage() {}
func (*RoverResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_cea399972d1e9fa7, []int{7}
}
func (m *RoverResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RoverResponse.Unmarshal(m, b)
}
func (m *RoverResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RoverResponse.Marshal(b, m, deterministic)
}
func (m *RoverResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_RoverResponse.Merge(m, src)
}
func (m *RoverResponse) XXX_Size() int {
return xxx_messageInfo_RoverResponse.Size(m)
}
func (m *RoverResponse) XXX_DiscardUnknown() {
xxx_messageInfo_RoverResponse.DiscardUnknown(m)
}
var xxx_messageInfo_RoverResponse proto.InternalMessageInfo
func (m *RoverResponse) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *RoverResponse) GetPosition() *Vector {
if m != nil {
return m.Position
}
return nil
}
func (m *RoverResponse) GetRange() int32 {
if m != nil {
return m.Range
}
return 0
}
func (m *RoverResponse) GetSpeed() int32 {
if m != nil {
return m.Speed
}
return 0
}
type StatusResponse struct {
// The time the next tick will occur
NextTick string `protobuf:"bytes,1,opt,name=next_tick,json=nextTick,proto3" json:"next_tick,omitempty"`
// Whether the server is ready to accept requests
Ready bool `protobuf:"varint,2,opt,name=ready,proto3" json:"ready,omitempty"`
// The tick rate of the server in minutes (how many minutes per tick)
Tick int32 `protobuf:"varint,3,opt,name=tick,proto3" json:"tick,omitempty"`
// The version of the server in v{major}.{minor}-{delta}-{sha} form
Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *StatusResponse) Reset() { *m = StatusResponse{} }
func (m *StatusResponse) String() string { return proto.CompactTextString(m) }
func (*StatusResponse) ProtoMessage() {}
func (*StatusResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_cea399972d1e9fa7, []int{8}
}
func (m *StatusResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_StatusResponse.Unmarshal(m, b)
}
func (m *StatusResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_StatusResponse.Marshal(b, m, deterministic)
}
func (m *StatusResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_StatusResponse.Merge(m, src)
}
func (m *StatusResponse) XXX_Size() int {
return xxx_messageInfo_StatusResponse.Size(m)
}
func (m *StatusResponse) XXX_DiscardUnknown() {
xxx_messageInfo_StatusResponse.DiscardUnknown(m)
}
var xxx_messageInfo_StatusResponse proto.InternalMessageInfo
func (m *StatusResponse) GetNextTick() string {
if m != nil {
return m.NextTick
}
return ""
}
func (m *StatusResponse) GetReady() bool {
if m != nil {
return m.Ready
}
return false
}
func (m *StatusResponse) GetTick() int32 {
if m != nil {
return m.Tick
}
return 0
}
func (m *StatusResponse) GetVersion() string {
if m != nil {
return m.Version
}
return ""
}
type Vector struct {
X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"`
Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Vector) Reset() { *m = Vector{} }
func (m *Vector) String() string { return proto.CompactTextString(m) }
func (*Vector) ProtoMessage() {}
func (*Vector) Descriptor() ([]byte, []int) {
return fileDescriptor_cea399972d1e9fa7, []int{9}
}
func (m *Vector) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Vector.Unmarshal(m, b)
}
func (m *Vector) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Vector.Marshal(b, m, deterministic)
}
func (m *Vector) XXX_Merge(src proto.Message) {
xxx_messageInfo_Vector.Merge(m, src)
}
func (m *Vector) XXX_Size() int {
return xxx_messageInfo_Vector.Size(m)
}
func (m *Vector) XXX_DiscardUnknown() {
xxx_messageInfo_Vector.DiscardUnknown(m)
}
var xxx_messageInfo_Vector proto.InternalMessageInfo
func (m *Vector) GetX() int32 {
if m != nil {
return m.X
}
return 0
}
func (m *Vector) GetY() int32 {
if m != nil {
return m.Y
}
return 0
}
func init() {
proto.RegisterType((*Command)(nil), "rove.Command")
proto.RegisterType((*CommandsRequest)(nil), "rove.CommandsRequest")
proto.RegisterType((*Error)(nil), "rove.Error")
proto.RegisterType((*RadarRequest)(nil), "rove.RadarRequest")
proto.RegisterType((*RadarResponse)(nil), "rove.RadarResponse")
proto.RegisterType((*RegisterRequest)(nil), "rove.RegisterRequest")
proto.RegisterType((*RoverRequest)(nil), "rove.RoverRequest")
proto.RegisterType((*RoverResponse)(nil), "rove.RoverResponse")
proto.RegisterType((*StatusResponse)(nil), "rove.StatusResponse")
proto.RegisterType((*Vector)(nil), "rove.Vector")
}
func init() {
proto.RegisterFile("rove.proto", fileDescriptor_cea399972d1e9fa7)
}
var fileDescriptor_cea399972d1e9fa7 = []byte{
// 477 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0x5d, 0x6b, 0xdb, 0x30,
0x14, 0x8d, 0x93, 0x38, 0x75, 0x6f, 0x92, 0x15, 0xb4, 0x6e, 0x98, 0x94, 0x41, 0x10, 0x1b, 0x64,
0x2f, 0x2e, 0x64, 0x2f, 0x83, 0x3e, 0x8e, 0xfe, 0x01, 0x75, 0x14, 0xf6, 0x34, 0x14, 0xfb, 0xce,
0x98, 0x36, 0x96, 0x2b, 0xc9, 0x21, 0xf9, 0x49, 0xfb, 0x97, 0x43, 0xba, 0xb2, 0x97, 0x94, 0x8d,
0xf6, 0xed, 0x9e, 0xab, 0xab, 0x73, 0xce, 0xfd, 0x00, 0xd0, 0x6a, 0x87, 0x59, 0xa3, 0x95, 0x55,
0x6c, 0xec, 0xe2, 0xc5, 0x55, 0xa9, 0x54, 0xf9, 0x88, 0xd7, 0x3e, 0xb7, 0x69, 0x7f, 0x5d, 0xe3,
0xb6, 0xb1, 0x07, 0x2a, 0xe1, 0x3f, 0xe0, 0xec, 0x9b, 0xda, 0x6e, 0x65, 0x5d, 0xb0, 0x14, 0xce,
0x72, 0x0a, 0xd3, 0x68, 0x19, 0xad, 0xce, 0x45, 0x07, 0xdd, 0xcb, 0x06, 0xa5, 0xae, 0xea, 0x32,
0x1d, 0xd2, 0x4b, 0x80, 0x6c, 0x01, 0x49, 0xd1, 0x6a, 0x69, 0x2b, 0x55, 0xa7, 0xa3, 0x65, 0xb4,
0x8a, 0x45, 0x8f, 0xf9, 0x3d, 0x5c, 0x04, 0x6a, 0x23, 0xf0, 0xa9, 0x45, 0x63, 0x1d, 0x91, 0xcc,
0x73, 0xd5, 0xd6, 0xb6, 0x93, 0x08, 0x90, 0x7d, 0x86, 0x24, 0xa8, 0x99, 0x74, 0xb8, 0x1c, 0xad,
0xa6, 0xeb, 0x79, 0xe6, 0x3b, 0x09, 0x14, 0xa2, 0x7f, 0xe6, 0x1f, 0x20, 0xbe, 0xd5, 0x5a, 0x69,
0x76, 0x09, 0x31, 0xba, 0x20, 0x70, 0x11, 0xe0, 0x2b, 0x98, 0x09, 0x59, 0x48, 0xfd, 0xa2, 0x26,
0xbf, 0x81, 0x79, 0xa8, 0x34, 0x8d, 0xaa, 0x0d, 0x3a, 0x42, 0x2d, 0xeb, 0x12, 0x7d, 0x61, 0x2c,
0x08, 0xb8, 0xac, 0xad, 0x1e, 0xd1, 0xf8, 0xde, 0x67, 0x82, 0x00, 0xff, 0x04, 0x17, 0x02, 0xcb,
0xca, 0x58, 0xec, 0x95, 0x18, 0x8c, 0x6b, 0xb9, 0xc5, 0x20, 0xe3, 0x63, 0xef, 0x46, 0xed, 0xf0,
0x15, 0x6e, 0x0e, 0x30, 0x0f, 0x95, 0xc1, 0xcd, 0x3f, 0xe8, 0xd8, 0x0a, 0x92, 0x46, 0x99, 0xca,
0xcf, 0xdb, 0xd9, 0x99, 0xae, 0x67, 0x34, 0xa6, 0x7b, 0xcc, 0xad, 0xd2, 0xa2, 0x7f, 0xfd, 0xdb,
0xcb, 0xe8, 0x59, 0x2f, 0xa6, 0x41, 0x2c, 0xd2, 0x31, 0x65, 0x3d, 0xe0, 0x4f, 0xf0, 0xe6, 0xce,
0x4a, 0xdb, 0x9a, 0x5e, 0xfb, 0x0a, 0xce, 0x6b, 0xdc, 0xdb, 0x9f, 0xb6, 0xca, 0x1f, 0x82, 0x81,
0xc4, 0x25, 0xbe, 0x57, 0xf9, 0x83, 0xa7, 0x46, 0x59, 0x1c, 0xbc, 0x83, 0x44, 0x10, 0x70, 0x76,
0x7d, 0x35, 0xe9, 0xf9, 0xd8, 0x75, 0xbb, 0x43, 0x6d, 0x9c, 0xdb, 0x31, 0x75, 0x1b, 0x20, 0xff,
0x08, 0x13, 0xb2, 0xcc, 0x66, 0x10, 0xed, 0xc3, 0xc0, 0xa3, 0xbd, 0x43, 0xc4, 0x1b, 0x8b, 0xe8,
0xb0, 0xfe, 0x3d, 0x84, 0xa9, 0x1f, 0xca, 0x1d, 0xea, 0x1d, 0x6a, 0x76, 0x03, 0x49, 0x77, 0x52,
0xec, 0xdd, 0xc9, 0x7d, 0x74, 0x27, 0xb6, 0x78, 0x9f, 0xd1, 0xb9, 0x67, 0xdd, 0xb9, 0x67, 0xb7,
0xee, 0xdc, 0xf9, 0x80, 0xad, 0x21, 0xf6, 0xeb, 0x66, 0x8c, 0x7e, 0x1e, 0x5f, 0xc9, 0xe2, 0xed,
0x49, 0x8e, 0xa6, 0xc0, 0x07, 0x4e, 0xb0, 0xdb, 0x72, 0x27, 0xf8, 0x6c, 0xeb, 0x2f, 0x08, 0x3a,
0xf3, 0xbd, 0xe0, 0xd1, 0x21, 0xf4, 0x82, 0xc7, 0x2b, 0xe7, 0x03, 0xf6, 0x15, 0x26, 0xb4, 0x0a,
0xf6, 0x1f, 0xde, 0xc5, 0x25, 0x7d, 0x3c, 0x5d, 0x18, 0x1f, 0x6c, 0x26, 0xbe, 0xee, 0xcb, 0x9f,
0x00, 0x00, 0x00, 0xff, 0xff, 0xed, 0x84, 0x22, 0xdd, 0x01, 0x04, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6
// RoverServerClient is the client API for RoverServer service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type RoverServerClient interface {
// Send commands to rover
//
// Sending commands to this endpoint will queue them to be executed during the following ticks, in the order sent
Commands(ctx context.Context, in *CommandsRequest, opts ...grpc.CallOption) (*empty.Empty, error)
// Get radar information
//
// Gets the radar output for the given rover
Radar(ctx context.Context, in *RadarRequest, opts ...grpc.CallOption) (*RadarResponse, error)
// Register an account
//
// Tries to register an account with the given name
Register(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*empty.Empty, error)
// Get rover information
//
// Gets information for the account's rover
Rover(ctx context.Context, in *RoverRequest, opts ...grpc.CallOption) (*RoverResponse, error)
// Server status
//
// Responds with various details about the current server status
Status(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*StatusResponse, error)
}
type roverServerClient struct {
cc grpc.ClientConnInterface
}
func NewRoverServerClient(cc grpc.ClientConnInterface) RoverServerClient {
return &roverServerClient{cc}
}
func (c *roverServerClient) Commands(ctx context.Context, in *CommandsRequest, opts ...grpc.CallOption) (*empty.Empty, error) {
out := new(empty.Empty)
err := c.cc.Invoke(ctx, "/rove.RoverServer/Commands", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *roverServerClient) Radar(ctx context.Context, in *RadarRequest, opts ...grpc.CallOption) (*RadarResponse, error) {
out := new(RadarResponse)
err := c.cc.Invoke(ctx, "/rove.RoverServer/Radar", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *roverServerClient) Register(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*empty.Empty, error) {
out := new(empty.Empty)
err := c.cc.Invoke(ctx, "/rove.RoverServer/Register", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *roverServerClient) Rover(ctx context.Context, in *RoverRequest, opts ...grpc.CallOption) (*RoverResponse, error) {
out := new(RoverResponse)
err := c.cc.Invoke(ctx, "/rove.RoverServer/Rover", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *roverServerClient) Status(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*StatusResponse, error) {
out := new(StatusResponse)
err := c.cc.Invoke(ctx, "/rove.RoverServer/Status", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// RoverServerServer is the server API for RoverServer service.
type RoverServerServer interface {
// Send commands to rover
//
// Sending commands to this endpoint will queue them to be executed during the following ticks, in the order sent
Commands(context.Context, *CommandsRequest) (*empty.Empty, error)
// Get radar information
//
// Gets the radar output for the given rover
Radar(context.Context, *RadarRequest) (*RadarResponse, error)
// Register an account
//
// Tries to register an account with the given name
Register(context.Context, *RegisterRequest) (*empty.Empty, error)
// Get rover information
//
// Gets information for the account's rover
Rover(context.Context, *RoverRequest) (*RoverResponse, error)
// Server status
//
// Responds with various details about the current server status
Status(context.Context, *empty.Empty) (*StatusResponse, error)
}
// UnimplementedRoverServerServer can be embedded to have forward compatible implementations.
type UnimplementedRoverServerServer struct {
}
func (*UnimplementedRoverServerServer) Commands(ctx context.Context, req *CommandsRequest) (*empty.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method Commands not implemented")
}
func (*UnimplementedRoverServerServer) Radar(ctx context.Context, req *RadarRequest) (*RadarResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Radar not implemented")
}
func (*UnimplementedRoverServerServer) Register(ctx context.Context, req *RegisterRequest) (*empty.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method Register not implemented")
}
func (*UnimplementedRoverServerServer) Rover(ctx context.Context, req *RoverRequest) (*RoverResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Rover not implemented")
}
func (*UnimplementedRoverServerServer) Status(ctx context.Context, req *empty.Empty) (*StatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Status not implemented")
}
func RegisterRoverServerServer(s *grpc.Server, srv RoverServerServer) {
s.RegisterService(&_RoverServer_serviceDesc, srv)
}
func _RoverServer_Commands_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CommandsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RoverServerServer).Commands(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/rove.RoverServer/Commands",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RoverServerServer).Commands(ctx, req.(*CommandsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RoverServer_Radar_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RadarRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RoverServerServer).Radar(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/rove.RoverServer/Radar",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RoverServerServer).Radar(ctx, req.(*RadarRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RoverServer_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RegisterRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RoverServerServer).Register(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/rove.RoverServer/Register",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RoverServerServer).Register(ctx, req.(*RegisterRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RoverServer_Rover_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RoverRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RoverServerServer).Rover(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/rove.RoverServer/Rover",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RoverServerServer).Rover(ctx, req.(*RoverRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RoverServer_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(empty.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RoverServerServer).Status(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/rove.RoverServer/Status",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RoverServerServer).Status(ctx, req.(*empty.Empty))
}
return interceptor(ctx, in, info, handler)
}
var _RoverServer_serviceDesc = grpc.ServiceDesc{
ServiceName: "rove.RoverServer",
HandlerType: (*RoverServerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Commands",
Handler: _RoverServer_Commands_Handler,
},
{
MethodName: "Radar",
Handler: _RoverServer_Radar_Handler,
},
{
MethodName: "Register",
Handler: _RoverServer_Register_Handler,
},
{
MethodName: "Rover",
Handler: _RoverServer_Rover_Handler,
},
{
MethodName: "Status",
Handler: _RoverServer_Status_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "rove.proto",
}

103
pkg/rove/rove.proto Normal file
View file

@ -0,0 +1,103 @@
syntax = "proto3";
package rove;
import "google/protobuf/empty.proto";
service RoverServer {
// Send commands to rover
//
// Sending commands to this endpoint will queue them to be executed during the following ticks, in the order sent
rpc Commands(CommandsRequest) returns (google.protobuf.Empty) {}
// Get radar information
//
// Gets the radar output for the given rover
rpc Radar(RadarRequest) returns (RadarResponse) {}
// Register an account
//
// Tries to register an account with the given name
rpc Register(RegisterRequest) returns (google.protobuf.Empty) {}
// Get rover information
//
// Gets information for the account's rover
rpc Rover(RoverRequest) returns (RoverResponse) {}
// Server status
//
// Responds with various details about the current server status
rpc Status(google.protobuf.Empty) returns (StatusResponse) {}
}
message Command {
// The command to execute, currently only accepts move, which requires a bearing and a duration.
string command = 1;
string bearing = 2;
int32 duration = 3;
}
message CommandsRequest {
string account = 1;
repeated Command commands = 2;
}
message Error {
// An explanation for the HTTP error returned
string error = 1;
}
message RadarRequest {
string account = 1;
}
message RadarResponse {
// The range in tiles from the rover of the radar data
int32 range = 1;
// A 1D array representing range*2 + 1 squared set of tiles, origin bottom left and in row->column order
bytes tiles = 2;
}
message RegisterRequest {
string name = 1;
}
message RoverRequest {
string account = 1;
}
message RoverResponse {
// The name of the rover
string name = 1;
// Position of the rover in world coordinates
Vector position = 2;
// The range of this rover's radar
int32 range = 3;
// The speed the rover can move per tick
int32 speed = 4;
}
message StatusResponse {
// The time the next tick will occur
string next_tick = 1;
// Whether the server is ready to accept requests
bool ready = 2;
// The tick rate of the server in minutes (how many minutes per tick)
int32 tick = 3;
// The version of the server in v{major}.{minor}-{delta}-{sha} form
string version = 4;
}
message Vector {
int32 x = 1;
int32 y = 2;
}

View file

@ -1,246 +0,0 @@
swagger: "2.0"
info:
description: "Rove is an asychronous nomadic game about exploring a planet as part of a loose community"
version: "0.1"
title: "Rove Server"
host: "api.rove-game.com:8080"
basePath: "/"
tags:
- name: "server"
description: "Interactions with the server itself"
- name: "accounts"
description: "Access to accounts"
- name: "rove"
description: "Operations for the game"
schemes:
- "http"
consumes:
- application/json
produces:
- application/json
paths:
/status:
get:
tags:
- "server"
summary: "Server status"
description: "Responds with various details about the current server status"
operationId: "Status"
responses:
"200":
description: "Server is active"
schema:
$ref: '#/definitions/status'
/register:
post:
tags:
- "accounts"
summary: "Register an account"
description: "Tries to register an account with the given name"
operationId: "Register"
parameters:
- in: body
name: name
schema:
$ref: '#/definitions/register-data'
responses:
"200":
description: "Successfully created account"
"400":
description: "Bad request, typically due to duplicate name"
schema:
$ref: '#/definitions/error'
"500":
description: "Server encountered error, please report this as a bug"
schema:
$ref: '#/definitions/error'
/{account}/commands:
post:
tags:
- rove
summary: Send commands to rover
description: "Sending commands to this endpoint will queue them to be executed during the following ticks, in the order sent"
operationId: "Commands"
parameters:
- in: path
name: account
required: true
type: string
- in: body
name: commands
schema:
$ref: '#/definitions/commands-data'
responses:
"200":
description: "Successfully queued commands"
"400":
description: "Bad request, typically due to unknown command parameters"
schema:
$ref: '#/definitions/error'
"500":
description: "Server encountered error, please report this as a bug"
schema:
$ref: '#/definitions/error'
/{account}/radar:
get:
tags:
- rove
summary: Get radar information
description: "Gets the radar output for the given rover"
operationId: "Radar"
parameters:
- in: path
name: account
required: true
type: string
responses:
"200":
description: "Successfully returned rover radar"
schema:
$ref: '#/definitions/radar-response'
"400":
description: "Bad request, typically due to unknown account"
schema:
$ref: '#/definitions/error'
"500":
description: "Server encountered error, please report this as a bug"
schema:
$ref: '#/definitions/error'
/{account}/rover:
get:
tags:
- rove
summary: Get rover information
description: "Gets information for the account's rover"
operationId: "Rover"
parameters:
- in: path
name: account
required: true
type: string
responses:
"200":
description: "Successfully returned rover information"
schema:
$ref: '#/definitions/rover-response'
"400":
description: "Bad request, typically due to unknown account"
schema:
$ref: '#/definitions/error'
"500":
description: "Server encountered error, please report this as a bug"
schema:
$ref: '#/definitions/error'
definitions:
status:
properties:
ready:
description: Whether the server is ready to accept requests
type: boolean
example: true
version:
description: The version of the server in v{major}.{minor}-{delta}-{sha} form
type: string
example: "v0.12-1-g7d1a2d7"
tick:
description: The tick rate of the server in minutes (how many minutes per tick)
type: integer
example: 5
nexttick:
description: The time the next tick will occur
type: string
example: "15:30:00"
error:
properties:
error:
description: An explanation for the HTTP error returned
type: string
example: "account not found"
register-data:
properties:
name:
description: The account name
type: string
example: "myname"
required:
- name
command:
properties:
command:
description: "The command to execute, currently only accepts move, which requires a bearing and a duration."
type: string
example: move
bearing:
type: string
example: NE
duration:
type: integer
example: 5
commands-data:
properties:
commands:
description: A set of commands to exectute in the order given
type: array
items:
type: object
properties:
schema:
$ref: '#/definitions/command'
radar-response:
properties:
range:
description: The range in tiles from the rover of the radar data
type: integer
example: 5
tiles:
description: A 1D array representing range*2 + 1 squared set of tiles, origin bottom left and in row->column order
type: array
items:
type: integer
vector:
properties:
'x':
type: integer
example: 1
'y':
type: integer
example: 2
rover-attributes:
properties:
speed:
description: The speed the rover can move per tick
type: integer
example: 1
range:
description: The range of this rover's radar
type: integer
example: 5
name:
description: The name of the rover
type: string
example: rover-one
position:
description: Position of the rover in world coordinates
type: object
properties:
schema:
$ref: '#/definitions/vector'
rover-response:
properties:
attributes:
description: The attributes of the given rover
type: object
$ref: '#/definitions/rover-attributes'