In this chapter … I re-examine program flow, the responsibilities assigned to various methods and functions and the importance of having clear rules for maintaining program state. I also provide a short look at testing new changes. Read more of the Rogue C# series here.
I might have created an actual monster here and it’s demanding recognition.
The Rogue C# project has always been primarily about demonstrating the C# language and the emphasis has been on the documentation. The software and codebase itself were important but, let’s face it, it’s mostly a clone of the original Rogue for demonstration, not a marketable game that needs to be finished on a deadline.
It’s still a real game, though, and I don’t like putting half-finished code out there. The growing size of this documentation demands a usable final product. Also, the more I look at the code and its growing complexity, the more I realize I’ve created something more than a demo. This code has become a system that needs to be maintained and respected as software.
Anyway, on to the latest changes …

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 AndrewComeau.com. The current code for this project is also available on Github.
Don’t miss the next chapter! Subscribe for free on Substack to be notified when new chapters are posted.
When I looked at creating a title screen for the game, I ended up reviewing how the game output was being sent to the main form and realized something wasn’t quite right.
The DisplayMap array, which determines what the player actually sees on the screen, was being edited from multiple points. The MapText() method in the Game class is supposed to be the manager of this array but the GameOver() method was calling VictoryScreen() and RIPScreen() which both called UpdateDisplayFromText() from the MapLevel class to independently update the screen when the game ended. This was actually an improvement from the earlier setup where separate sections of code were doing it.

Meanwhile, we had three places in the Game class where the code was looking at the DisplayMode and the GameMode display property and deciding between MapText() and MapCheck() for output to the screen. KeyHandler() was still kind of a mess because, in the previous chapter where I cleaned it up, I was just fixing the problems that were happening then without looking at the whole picture. It was responding to a couple of the display states in the DisplayMode enumeration but ignoring the rest.
public enum DisplayMode {
Titles = 1,
Primary = 2,
Inventory = 3,
Help = 4,
GameOver = 5,
Victory = 6,
Scoreboard = 7,
}
This is an example of the kind of thing that can happen when a program gets big and is changed over a long period of time. Although this is a one-man project, the three-year gap in my work on it and my need to get to know the code again almost simulates having a new person working on an existing codebase which can cause its own issues.
That’s okay – I’ll just call it an evolution of the program.
Let’s try a few basic rules and see how it works.
- All turns end back at KeyHandler() so that’s where the screen restore should happen.
- KeyHandler must account for all DisplayModes.
- If another part of the Game class needs to affect the screen, it must happen through the changing of DisplayMode so that KeyHandler() can treat it as a request.
First, we have the Game.RestoreMap() method, which is one of the three places where the decision is being made between MapText() and MapCheck(). It needs to be the only one and the rest of the code needs to call it.
private void RestoreMap()
{
// Restore the map display.
if (GameMode == DisplayMode.Inventory || GameMode == DisplayMode.Help)
{
GameMode = DisplayMode.Primary;
if (DevMode)
CurrentMap.MapCheck();
else
CurrentMap.MapText();
}
}
The method shouldn’t be playing around with the DisplayMode, either. That’s the responsibility of whatever code is calling it and other code should be able to call it without the concern that it’s going to refuse because the current mode isn’t correct. The DisplayInventory() method is also setting the DisplayMode when it shouldn’t. The other code should be requesting a new display mode through the enumeration and the KeyHandler should be responding to it.
public void DisplayInventory()
{
string screenText = "Inventory List\n\n";
// Switch the screen to the player's inventory.
GameMode = DisplayMode.Inventory;
foreach (InventoryLine line in GameInventory.InventoryDisplay(CurrentPlayer.CharacterInventory))
if (line.InvItem == CurrentPlayer.Armor)
screenText += line.Description + " (being worn)\n"; // current armor
else if (CurrentPlayer.Wielding != null && line.InvItem == CurrentPlayer.Wielding)
screenText += line.Description + " (wielding)\n"; // weapon
else if (CurrentPlayer.RightHand != null && line.InvItem == CurrentPlayer.RightHand)
screenText += line.Description + " (on right hand)\n"; // ring
else if (CurrentPlayer.LeftHand != null && line.InvItem == CurrentPlayer.LeftHand)
screenText += line.Description + " (on left hand)\n"; // ring
else
screenText += line.Description + "\n";
this.CurrentMap.UpdateDisplayFromText(screenText);
}
Working outward from there, RestoreMap() is referenced from nine different places in the Game class which is way too many.
- Replace RestoreMap() with DisplayMode.Primary
- Replace DisplayInventory() with DisplayMode.Inventory
- Replace GameOver() with DisplayMode.GameOver or DisplayMode.Victory
This means that KeyHandler() needs to end with a check of the relevant display modes so the correct screen can be loaded. Since there’s a limited number of items in the DisplayMode enumeration, we don’t have to worry about this Switch statement getting crazy.
// Display the appropriate map mode
switch (GameMode)
{
case DisplayMode.Primary:
RestoreMap();
break;
case DisplayMode.Inventory:
DisplayInventory();
break;
case DisplayMode.Help:
HelpScreen();
break;
case DisplayMode.GameOver:
VictoryScreen();
break;
case DisplayMode.Victory:
RIPScreen();
break;
default:
break;
}
The truth is that I don’t even like multiple references to the MapLevel.UpdateDisplayFromText() method spread out through the class in DisplayInventory(), HelpScreen(), etc. so I’m going to change those methods into functions that will return the necessary string to display and have the Switch statement make the calls to the Update method.
// Display the appropriate map mode
switch (GameMode)
{
case DisplayMode.Primary:
RestoreMap();
break;
case DisplayMode.Inventory:
CurrentMap.UpdateDisplayFromText(DisplayInventory());
break;
case DisplayMode.Help:
CurrentMap.UpdateDisplayFromText(HelpScreen());
break;
case DisplayMode.GameOver:
CurrentMap.UpdateDisplayFromText(RIPScreen());
break;
case DisplayMode.Victory:
CurrentMap.UpdateDisplayFromText(VictoryScreen());
break;
default:
break;
}
This meant making another delegate method for when the user actually does request the inventory screen but that’s fine. That request from the user needed to be defined as not starting an actual turn, anyway.
Testing
“If you sit a hundred users down at a hundred different computers and give them a hundred hours of access to your program, it will develop intelligence for the sole purpose of having a nervous breakdown.”
– Anonymous
Since this game simply responds to key presses and runs the same code repeatedly over hundreds and thousands of cycles, my testing has been mostly playing through the game and seeing if specific features work. I expect that the sheer repetition of the code calls will expose any problems pretty quickly. That’s also the reason you haven’t seen any error handling (yet). However, I will need to do an upcoming chapter where I do my best to actually break the game as I’ve previously advised programming students to do.
Running the game, I loaded the inventory and help screens and hit ESC to return from each one – no problem. I also tried hitting other commands while I was in the screens and the program ignored it as it should have.
Testing the Victory screen was as simple as inserting a breakpoint when the player changes level. I went upstairs from Level 1 and was able to change the local variable.
When I tested the Victory screen, however, it seemed someone didn’t like my changes.
The “mysterious forces” death I coded is actually a signal from the code that no actual cause could be determined. It’s a silent, graceful error that alerts the developer and still gives the player a smooth experience although most users wouldn’t consider it a great one. It turned out I’d reversed the calls to the RIPScreen() and VictoryScreen() functions in the Switch statement. Once I fixed that, it worked fine.
Of course, I haven’t coded a scoreboard yet so the player will have to wait to see that actual place of honor but it’s a nice pat on the back for now.
Testing also means checking to see how the program responds to silly things the users shouldn’t do but probably will at some point. I found that when trying to read a non-scroll item, the game did not go back to the map after informing the player that there was nothing on the item to read. A simple change to the GameMode fixed that. Of course, it was predictable that trying to eat or quaff something inedible, trying to wear a mango as armor or trying to drop something on top of another item would have the same problem so I went ahead and fixed those, too. It’s good to be proactive.
So, now all roads lead to KeyHandler() and the code has a bit more of a coherent plan so long as I don’t break it again.
A few other minor changes …
- I fixed some of the naming in the project, standardizing the custom namespaces as “RogueGame” and changing the name of the actual project to “RogueCSharp”. The main form now has “Rogue C#” as a title instead of “Dungeon Map”.
- I removed all the unnecessary Using statements at the top of the various classes. Visual Studio inserts these by default but they were cluttering things up.
- As you saw earlier, the game now has a victory screen for when the player exits the dungeon. While adding and testing this, I discovered that Level 26 was hanging on map creation because the verification method was not finding Amulet of Yendor on the map. When I coded the search, I’d forgotten that the Amulet is not kept on the map but in the MapLevel inventory list. Once I fixed that search, everything worked again.
Some of the changes here are also in preparation to create a non-development release of the game. It’s great as a development project but it would be nice if people were able to play it without a full install of Visual Studio. In a future chapter, I’ll talk more about what it means to release a C# program into the wild.




