Text Adventure Time! (Command Handling)

Text Adventure Time! (Command Handling)

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 second post documenting my single user dungeon development adventure.

It’s time to fire up the git time machine and poke holes in my old code base! 

Code reviewing my old work is always an emotional roller coaster: disdain/slight embarrassment for the choices I made from lack of experience, pride for having miraculously gotten it working with such poor choices in the first place, and curiosity for discovering approaches I might have taken to improve the code at the time.

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

Get yourself set up to run the game and poke at the code

Repo:

Running the game:

In our last Java Text Aventure post, we left off at commit b0114f4. In this post I’ll be covering two of the next four commits (the first two commits don’t add much value), which primarily deal with updating the command processing.

Summary of changes

Note: to get this super readable output, I use the git alias put together by this fellow.

Checking out commits

A note about diff and show: There’s a lot of noise with a couple of these commits, where whole files appear to be deleted and re-added. This is because git saw the moving of a file to a subdirectory as a delete and an add.

This ugliness could and should have been avoided by following the commit early and often rule, which helps to keep change reasons distinct between commits. The rule wasn’t followed here, and that resulted in a commit diff which is almost completely useless. Ideally one commit would have been renaming the files, and a separate commit the actual changing of the file contents.

Fortunately, we can target a specific file in our diffs to help attenuate the noise.

To see all of the diffs since last time, use this command:

But that’s not very helpful. You can target a specific file, like the Readme.md like so:

But what I REALLY want to analyze is the change made in each commit. To see changes for a specific commit, use the show command:

Example:

The Command processing updates

Two important commits here:

  • 77a7e8f – implemented framework for enum command structure
  • 8fc5b7a – finalized the enum usage

Checkout the first commit

Go ahead and check out commit 77a7e8f

In 77a7e8f, I began implementing the skeleton enum as the command handler. This approach will work, but there are better tools at our disposal to accomplish the same thing in a much more understandable way.

The switch case

Switch case implementations can be difficult to fully grasp when there are many cases. They can also be dangerous if you’re not explicit in notating your fallthroughs (which was not done in this commit). A fallthrough is where the break clause is not included, either intentionally or unintentionally, which causes all the code until the first break is encountered to be executed, even if it’s under a different case. If this is not called out by the developer, it will be seen as a defect in the code. It’s also very easy for anyone maintaining the code to cause it to break. /endrant.

For example, entering ‘eq’, ‘equip’, or ‘equipment’ will all trigger the following switch case, and then break out of the switch:

The Commands.java diff

Take a look at the diff for Commands.java:

This gives us the old file at src/Commands.java, with a simple enum, and the new file at src/com/knightsofsomethingnotable/Commands.java. This new file violates all kinds of code quality rules (this is pretty horrid Java coding :D), but that’s OK! Bad code works well as a learning step to get the pieces wired together, and trying to explain how bad code is working usually highlights the problems and makes a better way obvious.

How the Commands class works: Each time the player enters a command, the text is upper-cased and then mapped to an enum (if there’s a match), and then passed to the TakeAction inner class to execute the command. This occurs in two lines of the while (playing) { loop of the main method in Main.java.

The first line creates the enum, then instantiates the subclass CommandTest inside Commands, giving it the obscure name of ‘value’ (what?). Commands is the class, Command is the name of the enum list, valueOf just returns a matching enum, and the assignment to value sets the value parameter on the inner class TakeAction.

The second line then invokes the CommandTest‘s TakeAction method (note: don’t capitalize method names; use pseudoCamelCase), passing in all the parameters any command could possibly need (holy potatoes, use a wrapper object instead, like World, or Room.)

Whew. Looking back at this code, it took me a couple minutes to figure out what was even happening. Ignoring the poor structure of Commands.java for a minute, putting so many operations on a single line can be confusing and wastes the time of anyone trying to understand what’s happening (including future you). The enum should definitely have been created in a separate step, in case there were problems. In fact, this whole operation is inside a try block, which means any problems would just get swallowed and trigger a “You can’t do that.” response.

Anyway, the guts of what this commit was trying to accomplish is in the switch case which executes the code in the case block matching the command.

Wire up the rest of the commands

This second important commit converts the input processing and wires up the rest of the commands to our enum class.

To make the commands work how they were working before (that is, process a string containing a command and a parameter for that command: ‘command some command parameter’), I needed to update the Input.java class. This is one of the main benefits of converting the commands from the if/else block in the main method, which used hard-coded command string lengths and looked at string indexes to get the secondary parameters. It makes the input command parsing a little more understandable.

Instead of just grabbing the entire input, upper-casing it, and sending it back to the main method, the Input.getInput() method now splits the string on the first space into an array. The command will be everything before the first space, and stored at index 0. The command parameter will be everything beyond the first space, and stored at index 1.

This shuffles the problem from string indexing to list indexing, and I no longer need to be concerned with the character lengths of the commands. To make it more consistent, if the command doesn’t have a space, the list still contains two elements, but the second is just a 0-length string, and gets ignored by the command processing code.

That’s all for now. If you have concerns about my confusingly implemented command handler classes, never fear! These were addressed in future commits. Stay tuned!

4 thoughts on “Text Adventure Time! (Command Handling)

Join the Discussion

%d bloggers like this: