If you work as a programmer, you’ll often find yourself adding features to existing software that someone else created. This means getting familiar with the code and making selective changes in a way that won’t disrupt anything else. As this project grows, you’ll see more and more of this as I add new features to match the original Rogue game.
At this point, our game can generate a random map and there are classes to control the game itself and define a player. Now I want to spread some gold around the rooms for the player to collect. In the process, we’ll be making some improvements to how the program handles Random values.
This post is part of a series on creating a roguelike game in C#. For the latest chapters and more information, please visit the Official Project Page on ComeauSoftware.com. The current code for this project is also available on Github.
Requirements and Decisions
Our original requirements mentioned gold but didn’t say how much gold should be available. From my playing of the game, I’d say about 50% of the rooms should have a gold stash with each stash being anywhere from just a few pieces to maybe 125 pieces. The leprechauns that you occasionally run into in the game might have hundreds of pieces, as you would expect. Orcs also like to hang onto gold but I haven’t noticed that they have more than the average stash.
Since I want to limit the number of rooms that have gold deposits, I’ll add this to the MapLevel class during the map generation phase. A stash of gold doesn’t need any special properties beyond the number of pieces so that leaves the question of whether to define that number in advance when the gold is placed on the map or determine it at random when someone picks it up.
Roguelike maps are supposed to be procedurally defined at the start of the level but that doesn’t mean that every single attribute has to be done in advance – monsters can and do spawn in the middle of a level, after all. Leaving the amount to be discovered later also allows for the player’s stats to influence the amount found which could provide an additional challenge. It also keeps the code simple for now so I think I’ll go with that.
First a couple of new constants:
private const char GOLD = '*';
private const int ROOM_GOLD_PCT = 50;
Since the presence of a gold deposit is decided on a room by room basis, the RoomGeneration() method seems like a good place to go next. It already has the boundaries of the room defined so we can limit the space that the new code works with which makes the program more efficient. The code can start at the end, right after the room corners are set.
An instance of the Random class is even already in place so I just need to add a couple of integer variables – goldX and goldY and initially set them both to 1. Remember that the current code for the project is also available on Github.
// Evaluate for a gold stash
if(rand.Next(1, 101) > ROOM_GOLD_PCT)
// Search the room randomly for an empty interior room space
// and mark it as a gold stash.
while (levelMap[goldX, goldY].MapCharacter != ROOM_INT)
goldX = rand.Next(westWallX + 1, eastWallX);
goldY = rand.Next(northWallY + 1, southWallY);
levelMap[goldX, goldY] = new MapSpace(GOLD, goldX, goldY);
That should be it. Let’s run it.
One of the amazing things about programming for me is how I’ll be completely oblivious to a mistake in my code until it throws an error like this and then I’ll instantly know where it’s coming from.
Setting the variables to 1 was the problem. That worked great for the stairway on the level because that code was run after the rest of the map was finished. This code is running before all the null spaces in the array have been filled in so it’s hitting a null value. It’s also outside the boundaries of the room that I’m wanting to work with so let’s add a line just inside the IF statement. to set the values to the current room.
goldX = westWallX; goldY = northWallY;
That seemed to work.
The only problem remaining is that the pseudo-random numbers generated by the Random class don’t always seem to be well “shuffled” so some maps end up with gold stores in only a couple of rooms and others have it in almost all. The one shown above just happens to be well balanced.
Going to Google, I searched for “C# Random class distribution” and came to a solution on StackOverflow that suggested a best practice of declaring the Random class object at class level and making it static, then using that instance throughout. I used the same name as I usually did so I was just able to comment out all the other calls to the Random class and let the program use this one.
// Random number generator
private static Random rand = new Random();
The static modifier means that the instance of the Random class and its values belong to the MapLevel class rather than a specific MapLevel object. It is not re-instantiated every time a new MapLevel object is created but maintains the same continuity of random values based on the same seed value which comes from the system clock.
This is especially important in this program because the MapLevel class was instantiating a Random object in four different places and, as you saw in another chapter, the map itself can take less than 1 millisecond which means that it was using the same seed value and potentially restarting the series.
A true test of this would involve running 100 or so maps and recording the count of gold stores on each level and that’s a little more than I want to do right now but it might be a project for later when I start looking at doing some real tests of the game.