Building a Single User Dungeon With Java: The Knights of Something Notable

I wanted to get my hands dirty while making something fun. I LOVE text adventure RPGs, and I LOVE MUDs, so I’m attempting to build a cross between the two: a Single User Dungeon (SUD).

A few notes on code quality

Most of this code is pretty rough. I was mostly trying things until they worked and reflecting on what went well and what didn’t. I do love that git captures this process for me. It’s like a time machine. I can go back and look at how awkward it was in the beginning.

I started with the skeleton code provided by this fellow. The zip can be found here. I know there are probably better ways to implement some of what he’s done here, but that’s not my focus right now.

You can fork my changes to the code here: hello-world-sud.

Or checkout the branch:

git clone --branch jta-1 https://github.com/HelloWorldForBeginners/hello-world-sud.git

You can compile and run the program from the command line with these commands:

cd src/
javac *.java
java Main

If everything worked, you should see this:

You are in a dungeon.
Exits: cell, south; mess hall, east.

You see: [shirt, jacket, shoes, boots]

There are creatures here: [platypus]

>

WARNING: Proceed with caution: that there creature is a mammal of action.

Initial Changes

First I added the Item class, so I could have an inventory as a list of objects rather than a list of strings. Then I duplicated that to make an Equipment object, so that I could handle equipping those items. THEN I realized I should probably use inheritance, and set up a GameObject class.

Converting from a list of strings to a list of objects was more of a challenge than it should have been, but with enough brandy, anything is challenging!

Program Flow

The basic flow of the program is this:

  1. Build the world (including populating rooms with items and monsters)
  2. In a loop: get and process the player’s command

Building the World

The main method in Main.java sets the world up by calling the World.build method.

Getting the Player’s Input

The program then waits at the Input.getInput method for the player to enter a command, then returns that as a string.

Input [9]: String input = in.nextLine();

Processing the Player’s Command

This part is a little embarrassing. What a long messy chain of if-else. I promise I found a better solution later on. The substringing to determine the target of commands such as check, attack, equip is also very poorly written. It’s not very clear what the string indexing is doing, and it’s easy to make mistakes when adding new commands.

Bonus points if you can find the potty humor (and more bonus points if you are as inspired as I was by the epic gem don’t shit your pants).

Simple status command

commit f8f03f768f

else if (input.equals("player")) {
    Player.printPlayerInfo(player);
}

Changing rooms

The code for the directionals is not very DRY, and there are unclear variables of x and y, which basically are coordinates on a grid which identify a room. Choosing south here causes the current location on the grid to increment by 1 in the y direction.

commit f8f03f768f

} else if (input.equals("s") || input.equals("south")) {
    if (y < HEIGHT - 1) {
        y++;
        World.print(room, x, y);
    } else {
        System.out.println("You can't go that way.");
    }
}

Setting up the Item and Equipment Objects

commit f8f03f768f323aaf909d62b5adc2ead1e9f89be8
fixed item object contructor. added a few commands. enforced equipment attribute when trying to equip an item from inventory
commit 28c41a819fc1ee2ed19c1454638b3f1eda08b2b3
HashMap for equipment! Equip same slot = unequips anything in it first

There were a few issues.

The primary issue was that anything could be equipped. Like toilet paper. While I found this vastly amusing, it didn't make much sense from a practical standpoint, so I needed a way to restrict whether an item could be equipped.

The other issue was that my previously attempted equip code was overwriting the item in the target equipment slot when equipping a new item, which doesn't make sense, and would be pretty annoying to a player. The player should be able to take it off and sell it for hard cashey-money. So I implemented a check for the map. If the slot exists in the map, find the item and remove it. Then equip the next item.

Basically, the Equipment.equipItem method performs a series of checks and acts accordingly. Does the item exist in the inventory? Is it already equipped? Is there already something equipped to that slot?

NOTE: There is far too much going on in the Equipment.equipItem method, and the logic is not as clear as it should be, but I fixed that later on.

if (i.getType().equals("equipment")) {
    isEquipment = true;
}

Defining the Character Object

commit 77c10a978e5cf41617b88bbb779959f0b66502c6
added character object. player and nonPlayer extend character. inventory and equipment managed at the character level

I was considering how to add loot to monsters, and how to separate monsters from NPCs, so I added a base class with properties for inventory, equipment, etc., then inherited the class with the Player and NonPlayer classes.

Combat! (Sort of)

commit b0114f4e7eb3fb932478587f23801594b8e7b052
added combat!

Here we have rudimentary combat with half-baked math: monsterHealth = monsterHealth - playerAttack. Still very clunky. The command is basically just attack thing. The thing hits the player first, the player hits the thing, unconsciousness checks are performed, experience is rewarded where due. Very repetitive, no shortcuts, no loot drops, no forced combat (creature would ignore you if you don't attack again). Patience, these will happen with time.

Player Unconsciousness Check

// if player dead
// set hp to max hp (player and target), load cell, playermoney = playermoney * 0.9
if (playerCurrentHitPoints <= 0) {
    player.setHitPoints(playerMaxHitPoints);
    player.setMoney((int) Math.round(playerMoney * 0.9));
    System.out.println(player.getName() + " has been knocked unconscious! " + (playerMoney - player.getMoney()) + " money has been lost!");
    // load cell
    return;
} else {
    player.setHitPoints(playerCurrentHitPoints);
    System.out.println(player.getName() + ": " + playerCurrentHitPoints + "/" + playerMaxHitPoints);
}

That's Probably Enough

There you have it. A really basic, needs-work implementation of a single user dungeon (SUD) game (I'm not sure people call them that, but I like the way it sounds, and this is more MUD-like than text adventure, but without the 'Multi').

Leave a Reply