Seam Navigator

A photo of an old fashioned compass with a blurred map in the background. Photo by Dunamis Church on Unsplash.

Context

During the pandemic I ran a D&D game for a group of my friends. The game was set in a mythical world where airships would fly between different floating cities, towns and villages. This world was known as “The Seam”.

Some of the routes between these towns were well established “chalked” routes lit with lights, whilst others were not as well established. Navigating between these non-established routes was far more dangerous due to the perpetual darkness that covered the world of the Seam. Flying like this was known as flying “through the black”.

As the game went on, I found that calculating the distances the players were travelling to be pretty time consuming without a tool to do the maths for me. At the same time, I was also interested in trying out React. The result was “Seam Navigator“, a web tool that finds the shortest path between two locations in the Seam.

Layout

I used React to create the tool as I’d never played with it before and I wanted to learn how it worked. To keep things simpler on the CSS side, I used Bootstrap for all of the styling.

Aside from the taskbar heading, I knew that there would be two main parts to the tool:

This translated to three React components in the final project:

Path Finding

The map is defined in a JSON file. It contains two fields:

The tool needed to support two forms of path calculations. The first method calculates the shortest distance between two locations using chalked routes. I used Dijkstra’s algorithm to calculate the shortest route, calculating the distances between each node using their relative co-ordinates.

However, this method doesn’t cover locations that don’t have chalked routes to them. To calculate a path to those locations, the code first finds the chalked route to the node closest to the location using Dijkstra, and then calculates the direct distance to the final location from there.

Maintaining State

This was my first react project so it took me some experimentation before I found a configuration that I was happy with when it came to maintaining state.

My original design had the route cards and stops maintain their own state. Each Stop would update it’s state with the selected value, and report the name of a selected node to the Plotter via the onChanged event. Each RouteCard would also do it’s own routing calculations based on the nodes provided.

However, this proved to be difficult to manage. One critical bug occurred when deleting a Stop from the route if it was in-between two other stops. The route cards would update correctly, calculating the route between the two now-adjacent stops. However, the loop that renders the Stop inputs would be unable to update the content of the stop inputs with the new route locations. This led to the stop at the index of the now-deleted stop rendering the input text for the deleted stop instead of it’s own.

In the end managing the state of the Stop content and the RouteCard content entirely within the Plotter component fixed this bug and also meant that all of my business logic for calculating the route from a set of stops could be contained within one class instead of three.

I’m still not sure if this is the best way to manage state in React. There might be more effective patterns but I found that, for this app at least, this approach was a good way of maintaining separation of concerns.

Summary

A screenshot of the final application showing the stops and the route cards

The tool is available at seam-navigator.callumh.io for anyone to test out. The code is also available on my GitHub at CallumHewitt/seam-navigator.

Overall, this was a really fun project that was helped to make the D&D games run more smoothly when it came to navigation. It also taught me a lot about React and in particular about managing state between components. I would definitely be keen to try out react again in the future on some other projects.