Project A Part 2: The First Seven Days

This post is part of a series. Part one is here.

Work on Project A began 16 June. I used nguillaumin’s excellent slick2d-maven project to get off the ground in a hurry. I enjoy working with Slick2D. It’s opinionated, but not too much. It has its flaws, but none of them are enough to disparage its use for simple projects. The first commit was just committing the generated directory structure after generating it. From here, I tried to make my earlier goal a little more actionable:

My goal is to get as quickly as possible to a point where the player can interact with the game in a way that is fun. Everything else is secondary.

Sublime Text minimal of all the bugs I had filed against myself.

I had amassed a pretty big bug list working on my last project (see my Sublime Text minimap on the right). Some of it was actual bugs, logic issues, that sort of thing. The majority of it was pipe dreams about what I thought I might be able to create. What I wanted to do this time around was not get wrapped up in the future, but always focus on the next immediate thing which would get me to a playable game.

I just recently played golf for the first time in a long time. I remembered the rule that the player with the ball furthest from the hole plays next. I enacted a similar rule for my game’s facets. I would stop work on more sophisticated facets until the less finished ones caught up. It’s not foolproof—creating equally sized goals for each part of the game is hard—but it’s a good rule of thumb.

The first facet I chose is UI. My first three goals, referred to by letter, looked like this:

  1. A basic UI that can display text.
  2. Store previous displayed text in a ring buffer so that text panels can continuously add lines to it and older lines will fall off the screen.
  3. Display a map of the world.

Alongside it, I had the Worldgen facet. My first three goals looked like this:

  1. The initial world generation loop: one pre-pass, one pass, one post-pass.
  2. World generation parameters keyed to a random seed to guarantee consistency.
  3. Placing settlements of various sizes onto the world.

I first started working on the UI, which can be see in my earlier post. The first step was creating some abstractions. I wanted to hide the details of displaying characters and setting colors. I could then create text panels, add lines to them, and then display them in Slick2D’s update loop. I had finished up with this in about two days.

Once I reached this goal, I put UI aside, and started working on Worldgen. This was kind of cheating, since I pulled in a ton of useful source code from an old project to get off the ground faster. It provided the basic container classes, and a noise function to use as the elevation of the world.

To keep things moving along, I also pulled in a bunch of utilities that I had put together over the years. One of my favorites is the RandomGenerator class, an extension to java.util.Random that allows a handful of useful functions:

  • nextString(int length), which returns a random string of ASCII characters,
  • nextAvailable(List<T> list, Class<T> cls), which returns a random typed object from a list, as well as a variant for returning a random typed object from a collection like a Set.
  • roll(int qty, int sides) for D&D style dice rolls,
  • RandomEnum, which allows one to register an Enum in the generator and then ask for a random item, or any random item except a passed list of values.

This is part of the codebase I’d eventually like to get online and public. At that point, all one has to do is specify a seed to the RandomGenerator, and make sure the same one is used for all procedural generation steps. In this way one can have reproducible worlds based on a seed, similarly to Minecraft.

A test of generating random characters and colors.

When the (extreme) basics were in place to my satisfaction, I turned back to the UI. In this way, every part of the game keeps in step with the other parts. I am kept in check against rabbit-holing too deeply on any one aspect of the game’s engine. After one more test of my UI (seen on the left), I plugged in the world generation to the UI.

Finally getting to see my world in my UI.

This is always one of the best parts of game programming for me. The ability to see one’s results so clearly through your game’s UI. I just created that little world. It’s not going to survive for long, and the ‘elevation’ is just choosing a different color based on some internal value, but that’s a world, and I generated it.

To view the entire map, I created a subclass of my text panel which took a ‘renderable thing’, and allowed a cursor move atop it. The renderable thing in this case was just the world tiles, which I refer to as cells generally. Again, pulling out code from the past helped me a ton here. In countless games, I’ve written some variant of the following logic to track something on a map larger than the screen:

  if (cursorX < 0) cursorX = 0;
  if (cursorY < 0) cursorY = 0;

  if (cursorX >= tileRenderable.getWidth()) cursorX = tileRenderable.getWidth() - 1;
  if (cursorY >= tileRenderable.getHeight()) cursorY = tileRenderable.getHeight() - 1;
  
  viewport.setCenterX(cursorX);
  viewport.setCenterY(cursorY);
  
  if (viewport.getX() < 0) viewport.setX(0);
  if (viewport.getY() < 0) viewport.setY(0);
  
  if (viewport.getMaxX() > tileRenderable.getWidth())
    viewport.setX(tileRenderable.getWidth() - viewport.getWidth());
  if (viewport.getMaxY() > tileRenderable.getHeight())
    viewport.setY(tileRenderable.getHeight() - viewport.getHeight());

I let myself play with the UI just a little bit more, to create an input text panel so I can see and change the world seed. After that, it was back to worldgen. My next step was settlement generation. I decided to use YAML to store structure data. YAML is more susceptible to typos and malformed data than XML, but it is simpler and easier to write in a hurry. I needed a YAML parser, and after some research, settled on SnakeYAML.

SnakeYAML is more than just a YAML parser. It comes packed with all kinds of insane functionality. It can marshal YAML documents into custom types. It can use YAML tags as syntactic hints for parsing. It can create objects based on YAML strings which match custom regexes. An example (and one I shamelessly copied from the documentation) is the ability to create a document like the following:

!structure
id: small_house
name: small house
size: 2
contains:
  !r knowledge: 1d3
  !r water: 1d10
  !r food: 1d10
person_capacity: 4
rarity: 1d4

There’s a couple things at work here. Normally, this document would get stored as a HashMap. Adding the !structure tag tells SnakeYAML to store the document in a JavaBean-like class. Values specified like a D&D dice roll (e.g. 1d10) get parsed out into a Dice(int qty, int sides) object. Finally, the !r represents a specific Enum; in this case, a resource type. My Structure object has a Map<Resource, Dice> field called contains. SnakeYAML is smart enough to store the enum as the key, and the Die as the value. This is insanely powerful.

I used the rarity field of a structure as a kind of placement decision system. The above structure has a rarity of 1d4, so one out of every four buildings, on average, should be an old house. More rare buildings, like police offices, would be 1d30. Even more rare buildings, like museums, would be 1d100. I wanted to adhere to these rarities during world generation, so I used this quick and dirty method. When worldgen requested a building, I would iterate over all known buildings, and roll a die matching their rarity. Every building whose die roll came back one was added to a set. Finally, I chose a building at random from that set. This is not an efficient way of ensuring the distribution—and the distribution is most likely off—but it sufficed for my purposes.

The final part of settlement generation was the ability to generate “rare settlements”. Every once in a while the rarities of all the buildings were pulled inward on the number line towards their average. For example, say I only had two buildings in my database. One has a rarity of 1d4, and another has a rarity of 1d50. The average of 50 and four is 27, so 4 would become larger—to be closer to 27—and 50 would become smaller. These settlements had a higher than average amount of rare buildings. Generating these makes worldgen more interesting, and gives players something to look for.

With that, I the first week of writing code ended. I pulled in a lot of extant utilities I wrote from earlier projects. I relied on the knowledge I had picked up from ASCII UI libraries like libtcod. I’m not sure how long this would have taken me starting from scratch. In any event, now was the time to add more facets to the project. The next facet was NPCs, for which I’ll soon have a write-up.

The game UI with a world dotted with settlements, and a log on the right displaying the kind of buildings that were recently generated.