The Map
The Map is one of the game’s core services. It is implemented in Java using WebSphere Liberty. The source is available on GitHub. The public REST API is browsable with Swagger. What follows is a very description of how the internals of the map service work.
API, Language, and Runtime
The Map is a Java-based service. It uses Java 8, and relies on Java EE 7 technologies like JAX-RS 2.0.
We built the Map using WebSphere Liberty, in a Swagger-first manner using
Liberty’s apiDiscovery-1.0
feature. Swagger annotations were added to
JAX-RS resources and POJOs, with the set being refined until the API was
consistent. The annotated POJOs are then shared between API requests and
DB interactions.
NoSQL DataStore
The Map uses couchdb when running locally in docker containers, and Cloudant when running in Bluemix.
The ektorp library is used to interact with either backend, consistent with the replaceable-services aspect of twelve factor applications.
Maintaining the grid
The Map uses a 2-dimensional grid containing all known (both registered and empty) rooms. It ensures that registered rooms have neighbors to the North, South, East and West. It ignores Up and Down.
First Room has type room
, but is primordial. It is always at (0,0).
Map documents
Site
documents are used to represent a position in the grid.
There are two types of sites: empty
, for unassigned sites, and room
, for
assigned sites.
When returned from Map operations, a Site
contains additional generated data
describing exits.
Room Registration
The room registration API expects a JSON structure describing the room as a parameter. This includes connection details and descriptions of the room’s doors.
The Nature of Doors
A door is a description, as seen from the outside of the room. The east door (e) description should describe how a player traveling east will approach your room.
Describe the doors for your room (in green) from the point of view of the navigating player.
The example below shows the descriptions of the doors for First Room, which avoids any mention of direction in its door descriptions, though they are all consistent in theme.
In the game, if I /go N
from First Room, and get the /exits
, this
is the result:
Visible exits: (S)outh A knobbly wooden door with a rough carving or a friendly face (E)ast A shiny metal door, with a bright red handle (W)est An overgrown road, covered in brambles (N)orth A winding path
Note that the south exit uses the north door description from First Room!
Finding Neighbors
The map uses a view that shows a site’s neighbors in two ways:
- To generate the list of exits when a Site is retrieved
- To ensure that assigned sites have neighbors on all 4 sides to make navigating assigned rooms easier.
Queries for neighbors are made using the site’s coordinates.
That there is crazy, right? But it does some magic. Every site adds itself at its own coordinate, and as a directional neighbor. So First Room, which lives at (0,0), shows up in the index 5 times: [0,0,"0", "room"], [0,1,"W", "room"], [0,-1,"E", "room"], [1,0,"S", "room"], and [-1,0,"N", "room"]. This allows First room to show up as a neighbor when we query using that neighbor’s coordinates.
To carry on with the example above, we can query for the room to the North of first room using its coordinates.
And there it is in the results, First Room is the southern neighbor. If we include the associated documents when we make this query (as we do), then we have all the information that we need to generate the exits for the room at (0,1).
Empty Rooms
One of the challenges of maintaining the map is keeping allocated sites centered around First Room (the origin of the map). We do this using a two step process.
The first step uses a view that specifically lists only empty sites.
If the document is a site (it has a coord element), and it has an empty
type,
then the site is added to the view with a complex index that includes a sort
order based on the absolute value of its individual coordinates.
This is a snapshot of live data, but you can see that the list of available empty sites have a consistent sort value of 4. An empty site at (-5,0) would have a sort value of 5, and would be at the bottom of the list. If someone deleted an existing room, let’s say from (1,0), that site would have a sort value of 1, and would appear at the top.
When adding a new site, we query this view and use the first empty site in the result. This works us around the origin in a spiral, keeping the map densely packed around the origin. Nifty!