Text Adventure Time! (Creature Management Threading)

Some time ago, when I was brushing off my Java, I wanted to get my hands dirty while making something fun. I LOVE text adventure RPGs, so that’s what I attempted to build.

This is the ninth post documenting my single user dungeon development adventure.

In my last Java Text Aventure post, I left off at commit 8958042, where I introduced object loading from JSON files.

In this post I’ll be covering the creature management feature! I think this is really cool, since it brings a little life to the game world. A separate thread is started which handles the creature population of the World, and manages their automatic movement between rooms!

Here is the list of posts published on this project so far!

Check out the repo for this post and follow along!


Target commits:

Running the game:
(yes that pesky properties file is still there, getting in the way)

Trigger the thread on the main method

CreatureManager is a new Runnable class which handles the spawning and moving of creatures. It’s invoked as a new Thread from the main method after the World is built. While the game is running (while Application.getPlaying is true), the CreatureManager thread will invoke the processCreatures method, then wait a period of time determined by the timeToWait parameter.

The method processCreatures invokes getRoomsWithCreatures, which returns a List of all Rooms containing creatures, and also updates the class variable creatureCount with the current total number of creatures in the World. If the List of Rooms is empty, or if the creatureCount is less than the maxCreatureCount, the spawnCreature method is invoked.

Spawning creatures

The method spawnCreature delegates to three methods: getRoomToAddCreatureTo, getCreatureToAdd, and addCreatureToRoom.

The first method, getRoomToAddCreatureTo (the one without the Room parameter in the signature), adds the current room and its exits to a list, then randomly chooses one of those rooms. This approach means that only the immediate and connecting rooms will have creatures added to them, regardless of how large the world is. As the player moves through the World, the spawn-eligible rooms will change.

The second method, getCreatureToAdd, returns the new creature to be added. NonPlayerDefaults already chooses a creature at random, so we just need to invoke the NonPlayer constructor with a new NonPlayerDefaults.

The third method, addCreatureToRoom, adds the returned creature to the returned Room. If the room being added to is the current room, a notification gets printed that a new creature has spawned. This prevents spawn messages when a creature spawns in an adjacent room.

Moving creatures

Back in processCreatures, there’s a check to see if the creatureCount is equal to 1. If it is, that means the only creature in the World is the one we just added, and it’s silly to move it right away, so we return. Otherwise, we set up an iterator on the roomsWithCreatures list, and perform some checks to determine how to handle each creature in each Room.

First, we get the next room in the iterable, and then get an Iterator from the list of creatures for that room. We want to clean up weak creatures, so we check whether the creature level is three or more levels below the Player, and if so, we remove the creature, then move to the next creature in the iterable. If the creature has an acceptable level, we randomly pick the next room from a list which includes the creature’s current room and its exits, then invoke the moveCreatures method to handle the actual move.

The moveCreatures method returns immediately if the iterator has no remaining creatures (this could actually be handled before invoking moveCreatures).

If there are still creatures to process, moveCreatures checks whether the creature to be moved is the current combat target, and skips it if it is. This is so that if the player is engaged in combat with a creature, the creature thread will leave it where it is to prevent null pointers when the player tries to interact with it.

If both of the above checks pass, the method goes ahead and moves the creature by invoking the World.addCreatureToRoom method, then removing the creature from the iterator.

Finally, we want to notify the player of the activities of the creatures in the player’s current room. If the room the creature is going to or from is the current room, we print out a message:

That’s it! Now we have a functioning creature manager thread running in parallel with the main thread. Keep an eye out for the platypus!

Refactoring a few things

Only three minor refactors here: the overloaded getRoomToAddCreatureTo, which was previously used to move existing creatures, has been changed to getRoomToMoveCreatureTo; shouldCreatureMove has been added to give the creature a chance to not change rooms; and the combat check has been encapsulated in checkIfCombatTarget.

In the updated getRoomToMoveCreatureTo, I used a lambda to return a single key-value pair by removing all directions but the selected direction from the HashMap of exits copied from the Room’s exits property. This allows me to pass the direction and the room instead of just the room, and I can more clearly indicate to the player how the creatures are interacting with the room. Before: “Creature has exited the room”, after: “Creature has exited west”.

Matthew Odle

Indie dev, autodidactic polyglot, lover of programming, dad, musician, writer, mentor

Join the Discussion