C# Text Adventure: Lesson 15

Thinking about our Game Objects

Since we aren't intending for any of our code to be used by other programmers' assemblies in the future, we could make our game objects derive from nested classes found within class TextAdventure in heroes.cs. However, our heroes.cs source code file is already over 260 lines of code long, and will only get bigger.

So, let's use a separate source code file to define our game objects, and we'll simply create a library from it and our heroes.cs code will reference it at compile time. We'll make the classes (and their methods) that it contains public so that any assembly that wants can use them. After all, when we get done creating our GameObjs.dll library, numerous developers from across cyberspace will undoubtedly seize upon it.

You can download the source files for this lesson by clicking here.

Return to Lesson 14 | Proceed to Lesson 16

Return to Index

C# Text Adventure: Lesson 14

Expanded Logging and Refactored Code

The good news for this lesson is that there are so many modifications to the code of heroes.cs that you aren't going to be asked to type or copy/paste it at all. Just download this updated copy of the project source files and and extract them into your heroes project directory.

After doing that, reload heroes.cs in Notepad++, and take a gander. What you're seeing is the result of some refactoring in Main() and some additional logging. I've added code to methods deleteGame(), **exitGame(), **helpGame, **loadGame(), newGame(), and welcomeMsgAtAppStartup(). Run build.bat. If you downloaded and extracted the source code files for this lesson, heroes.cs should compile without a hitch. Run the program, and choose Main Menu items 1, 2, 4, 5 and 3, in that order.

After the program exits, take a look at execLog.txt in the logs subdirectory. In it, you'll find logged output from the program. Close execLog.txt and delete it. Then return to heroes.cs in Notepad++ and edit a single line of code:

In method **initVars(), find the following line:

blnLogExecution = true;

Change its assignment to false. Save heroes.cs and the rebuild the project with build.bat. Run heroes.exe again, then go to your logs subdirectory and look at execLog.txt again (yes, our heroes.exe program recreated it). But this time, with just a single line of data:

***   Another run of the program has started   ***

Just enough data to let us see that the program ran and logged output at some point, but no details. No DateTime stamps. No logged output from multiple methods in heroes.cs. As you can see, by setting a single boolean variable to true or false, we turn on/off logging. Set the blnLogExecution flag back to true.

Okay, that was part one of this lesson. We'll expand logging when and where we deem fit as we continue developing our text adventure game.

Now I'll briefly point out the three methods that make this logging possible. They are at the bottom of source code file heroes.cs: GetCurrentMethod(), enteringMethodLogEntry(), and exitingMethodLogEntry(). They required the addition of System.Runtime.CompilerServices and System.Diagnostics using statements at the top of the source code file. I won't go into more detail, except to say that the special attribute (it's in brackets just before GetCurrentMethod()) method GetCurrentMethod() is able to get the name of the current method on the stack.

Also, notice that I've done some refactoring in our while loop in Main():

Two of the three methods shown in the above screen shot were already in our code: welcomeMsgAtAppStartup() and presentMainMenu(). I've added logLoops() for this lesson, as well a code to increment our loop-counting variable.

As you might expect, the code within logLoops() will only execute if our blnLogExecution flag is set to true at compile time.

Mainly for shits and giggles (though I may think up some uses for it), I decided to track the number of iterations of the while loop during the execution of our game program, heroes.exe. To that end, I added a class-level variable of type long, loopsMade. It gets incremented at the top of our while loop. As you can see in the code, I set a MaxLoops constant to 99+ million, and just have the code reset loopsMade to 1 if it's getting ready to exceed that value.

It's likely a user's computer would wear out or lose power before the user could reach 99+ million loops in the game.

Now we'll begin in the next lesson to think about game objects.

You can download the source files for this lesson by clicking here.

Return to Lesson 13 | Proceed to Lesson 15

Return to Index

C# Text Adventure: Lesson 13

Implementing Logging

In this lesson, we'll implement logging. Although we could use a debugger, I find the form of logging implemented here to be less fuss and to provide useful information on how our program behaves.

The first thing you should do is open ConsoleInputOutput.cs and go to the openTextFileAndWrite() method. Modify it per the following listing:

    public static void openTextFileAndWrite(string sData, string sFilename)
    {
        if (!File.Exists(sFilename))
        {
            using (StreamWriter sw = File.CreateText(sFilename))
            {
                sw.Write(sData + "\n");
            }
        }
        else
        {
            using (StreamWriter sw = File.AppendText(sFilename))
            {
                sw.Write(sData + "\n");
            }
        }
    }

The 

In Notepad++, the method should now look like this:

Save source file ConsoleInputOutput.cs.

Now, go to your heroes.cs source code file. Modify the code between the start of class TextAdventure and the declaration of Main() as follows:

    private static string gameDir;
    private static string logsDir;
    private static string execLog;

    private static bool blnLogExecution;
    private static bool blnShowWelcomeMsg;

Move the actual assignment of these variables into method initVars():

    private static void initVars()
    {
        gameDir = ConsoleIO.getAppDirectory();
        logsDir = gameDir + "logs\\";
        execLog = logsDir + "\\execLog.txt";
        blnShowWelcomeMsg = true; //because we've not yet shown user a welcome msg
        blnLogExecution = true;
    }

Then place an invocation to initVars() as your first line of code inside Main().

The code after initVars() and before the while loop should be changed as follows:

        if (blnLogExecution)
        {
            string p = "We've entered TextAdventure Main() and set blnShowWelcomMsg to " + blnShowWelcomeMsg.ToString() + " ";
            p += "and blnLogExecution to " + blnLogExecution.ToString() + ".";
            p = prependDTNow(p);
            ConsoleIO.openTextFileAndWrite(p, execLog);
        }

        if (ConsoleIO.isValidPath(logsDir))
        {
            bool result = ConsoleIO.createDirectory(logsDir);
        }
        Console.TreatControlCAsInput = true; //prevents ending if user presses Ctrl-C

Add the following method to heroes.cs:

    private static string prependDTNow(string p)
    {
        string result = " " + DateTime.Now.ToString() + ":\n" + p;
        return result;
    }

It should look like this in Notepad++:

Now switch to your command prompt and build your project with build.bat. If no errors have been introduced, heroes.exe should compile. If not, you can download this lesson's files and use them to overwrite the existing files in your heroes directory.

Once you get the project to build, run heroes.exe two or three times, then go to your logs subdirectory and notice that the log file is updated each time.

If we ever want to disable logging, we can set the value of variable blnLogExecution to false in initVars(). Later on in our development of the game, we may add the ability of the user to turn this logging on or off from within the game menu.

To finish this lesson, modify initVars() as follows:

    private static void initVars()
    {
        gameDir = ConsoleIO.getAppDirectory();
        logsDir = gameDir + "logs\\";
        execLog = logsDir + "\\execLog.txt";
        logSep = Environment.NewLine + Environment.NewLine;
        logSep += "\t***Another run of the program has started\t***" + Environment.NewLine + Environment.NewLine;
        ConsoleIO.openTextFileAndWrite(logSep, execLog);
        blnShowWelcomeMsg = true; //because we've not yet shown user a welcome msg
        blnLogExecution = true;
    } 

Method initVars() should now look like this:

And the following line to your variable declarations at the top of heroes.cs:

private static string execLog;

Now go up to the top of heroes.cs and ensure that the code between the start of class TextAdventure and the beginning of the while loop looks like this:

Save all source code files and rebuild heroes.exe using build.bat. If it won't compile, check source code for errors, as indicated by compiler error messages. If you're still not compiling successfully, download the source files using the link below.

Run the program and use the menu to quit. Now look at the execLog.txt file in the logs subdirectory:

Nice! we have some new lines in there to make things more readable. Okay, good job! Take a break, then proceed to the next lesson.

You can download the source files for this lesson by clicking here.

Return to Lesson 12 | Proceed to Lesson 14

Return to Index

C# Text Adventure: Lesson 12

Reviewing/Slimming our initial Game Loop

As we begin Lesson 12, let's make things a bit more efficient. Since build.bat builds the DLL libraries before compiling heroes.cs, our buildlibraries.bat file has become unnecessary. Move it to a subdirectory called old stuff.

Next, let's making it even easier to get to our workspace. Remove the heroes shortcut on the Desktop and also move prompt.bat to the old stuff directory.

Now create a file with Notepad++ on your Desktop and name it csharp.bat. Copy the following code to it and save it:

@ECHO OFF
cmd.exe /K "cd C:\cmdline\heroes && C:"

Double-clicking this batch file will open a command line prompt (as prompt.bat was accomplishing) and will change the directory to our heroes folder at the same time. We will accomplish in one double-click what had been requiring two.

Here, in pseudo-code, is what we're doing in heroes.cs:

using code provided by Microsoft, do the following...
declare a TextAdventure class
create three string variables to hold some directory paths
create a boolean variable to act as a flag
determine the game's directory path
based on that, create a logs directory if needed
begin the game loop
if the welcome msg hasn't already appeared, show it
now just loop, executing menu code as needed

It turns out the four variables declared at the top of the class don't need to be public. Make them private instead. They'll still be visible to the rest of the class, which is all that's needed:

    private static string gameDir;
    private static string logsDir;
    private static string errorLog;
    private static bool blnShowWelcomeMsg;

We actually don't need errorLog, so get rid of that variable declaration and the assignment to it later in the code of heroes.cs.

It also turns out that we don't need to have the following assignment statement each and every time the game loops:

Console.TreatControlCAsInput = true;

So move it, placing it before the while loop and just after the assignment to boolean variable result.

Let's move the lines of code that present menu choices to the user, and place them in their own method that we'll call from within the game loop. Create a private void method presentMainMenu() and move the five lines starting with Stringbuilder sb into it:

So now we have the following method:

    private static void presentMainMenu()
    {
        StringBuilder sb = new StringBuilder("\n\n");
        sb.Append(" (1) New Game\n (2) Load Game\n (3) Exit Game\n (4) Help");
        ConsoleIO.conShow("", "\\wh");
        ConsoleIO.conShow(sb.ToString());
        ConsoleIO.conShow("\\d");
    }

And the while loop (with the switch statement collapsed), now looks like this:

And here is our presentMainMenu() method:

The welcomeMsgAtAppStartup() method looks funky, right? But that's usually the case with code that does a lot of formatting. This method is responsible for producing the following welcome message that greets the user the first time the game loop loops:

Note: the default size of the Windows command prompt window (and of ConEmu console emulator) is 80 characters wide by 25 lines tall. The welcome message method is designed to fill the width of that size console window.

Methods deleteGame(), helpGame(), loadGame(), and newGame() currently just contain placeholder code. In fact, the only reason I full coded exitGame() in the previous lesson was so we can gracefully terminate the program.

Now add the two methods shown below to ConsoleInputOutput.cs:

You'll need to go to the top of the source code file and add the following using statement:

using System.Text.RegularExpressions;

It's needed when dealing with Regular Expressions.

Then, go back to heroes.cs and modify the block of code just prior to the game loop as shown below (getting rid of the two assignment statements that assign value string.Empty):

Now return to your createDirectory() method in ConsoleInputOutput.cs and modify it as shown below:

You'll only change the code in the catch portion of the method. Here are the two lines of code that replace the conShow() line:

            string errorMsg = "\n\nError while attempting to create directory '" + p + "': " + e.ToString() + "\\d";
            openTextFileAndWrite(errorMsg, getAppDirectory() + "\\logs\\errorLog.txt");

Good job! We found some improvements to make our code more efficient and functional. We can now log errors to a text file, when desired.

You can download the source files for this lesson by clicking here.

Return to Lesson 11 | Proceed to Lesson 13

Return to Index

C# Text Adventure: Lesson 11

Generating our Game Loop

Note: there is quite a bit of new code introduced in this lesson. While I recommend printing it out and studying it, then entering it by hand, I make it available for copying/pasting. Additionally, you can download the files here for your convenience. You might have to download them if you introduce an error or your copy/paste operations lead to indent problems.

First, a bit of code tweaking. In ConsoleInputOutput.cs, I want you to update method areYouSure() with the following (which contains a couple of cosmetic tweaks that don't affect the method's function):

    public static int areYouSure()
    {
        /*  This method loops until user either enters 'Y' for "yes" or 'N' for "No".
            It returns 1 if 'Y' was entered, 0 if 'N'. */

        Console.TreatControlCAsInput = true; //prevents ending if user presses Ctrl-C

        bool invalidInput = true;
        int result = -1;
        string response = string.Empty;

        while (invalidInput)
        {
            conShow("\\pAre you sure? (", "\\wh");
            conShow("Y", "\\ge");
            conShow(")es or (", "\\wh");
            conShow("N", "\\ge");
            conShow(")o ", "\\wh");
            conShow("", "\\ye");

            ConsoleKeyInfo cki = Console.ReadKey();
            response = cki.Key.ToString().ToUpper();

            if (response == "Y" || response == "N")
            {
                invalidInput = false;
                result = 1;
                if (response == "N") { result = 0; }
            }
            conShow("\\n");
        }

        conShow("", "\\ye");
        ConsoleColors.conWhiteOnBlack();
        conShow("\\n");
        return result;
    }

Save your changes.

Now, rename rebuild.bat to rebuildlibraries.bat, as it's a more descriptive name. You won't really need that batch file much because you're going to use the following one, named build.bat:

csc.exe @ConsoleColors.rsp
csc.exe @ConsoleInputOutput.rsp
csc.exe @heroes.rsp

Create and save build.bat.

Now, you're going to add the following three methods to ConsoleInputOutput.cs: createDirectory(), getAppDirectory(), and getNumberSelection() :

    public static int getNumberSelection(int max_selection)
    {
        //user will select menu item from 1 to max_selection
        int selection = 0;
        string nums = "123456789";
        bool notNumber = true;
        bool improperRange = true;

        while (notNumber || improperRange)
        {
            conShow("\\n");
            if (max_selection > 1)
            {
                //if there are more than 1 menu items to choose from...
                conShow("\\pYour selection (1 - ");
                conShow(max_selection.ToString() + ")? ", "\\wh");
            }
            else
            {
                //if there is only one menu item...
                conShow("\\pYour selection (1)? ", "\\wh");
            }

            conShow("", "\\ye");

            ConsoleKeyInfo cki = Console.ReadKey();
            //substring out the 'D' ("Decimal?") prefacing number entered
            string choice = cki.Key.ToString();

            if (choice.Length > 1)
            {
                choice = choice.Substring(1, 1);
            }
            if (nums.Contains(choice))
            {
                notNumber = false;
                selection = Convert.ToInt32(choice);
                if (selection <= max_selection)
                {
                    improperRange = false;
                }
            }
        }
        return selection;
    }

Now add the following getAppDirectory() to ConsoleInputOutput.cs:

    public static string getAppDirectory()
    {
        string path = System.Reflection.Assembly.GetExecutingAssembly().CodeBase;
        var directory = System.IO.Path.GetDirectoryName(path);
        path = directory.ToString();
        if (path.StartsWith("file:\\"))
        {
            if (path.Length > 6)
            {
                path = path.Substring(6, path.Length - 6);
            }
        }
        path = path + "\\";
        return path;
    }

And here's the code for method createDirectory():

    public static bool createDirectory(string p)
    {
        bool success;

        try
        {
            Directory.CreateDirectory(p);
            success = true;
        }
        catch (Exception e)
        {
            success = false;
            conShow("\n\nError while attempting to create directory '" + p + "': " + e.ToString() + "\\d");        
        }
        finally
        {
        }
        return success;
    }

Save ConsoleInputOutput.cs now that you've added methods getNumberSelection(), createDirectory() and getAppDirectory.

Finally, create a new source code file named heroes.cs, copy the following code into it, and save it (alternatively, you may want to simply download the file, since it's about 118 lines of code):

using System;
using System.Linq;
using System.Text;

public sealed class TextAdventure
{
    public static string gameDir;
    public static string logsDir;
    public static string errorLog;
    public static bool blnShowWelcomeMsg;

    public static void Main()
    {
        blnShowWelcomeMsg = true; //because we've not yet shown user a welcome msg
        gameDir = string.Empty;
        logsDir = string.Empty;
        errorLog = string.Empty;

        gameDir = ConsoleIO.getAppDirectory();
        logsDir = gameDir + "logs\\";
        errorLog = logsDir + "errorlog.txt";

        bool result = ConsoleIO.createDirectory(logsDir);

        while (true)
        {
            Console.TreatControlCAsInput = true; //prevents ending if user presses Ctrl-C
            if (blnShowWelcomeMsg)
            {
                welcomeMsgAtAppStartup();
                blnShowWelcomeMsg = false;
            }

            StringBuilder sb = new StringBuilder("\n\n");
            sb.Append(" (1) New Game\n (2) Load Game\n (3) Exit Game\n (4) Help");
            ConsoleIO.conShow("", "\\wh");
            ConsoleIO.conShow(sb.ToString());
            ConsoleIO.conShow("\\d");
            int choice = ConsoleIO.getNumberSelection(4);
            ConsoleIO.conShow("\n\n", "\\wh");

            switch (choice)
            {
                case 1:
                    newGame();
                    break;
                case 2:
                    loadGame();
                    break;
                case 3:
                    exitGame();
                    break;
                case 4:
                    helpGame();
                    break;
            }
        }
    }

    private static void welcomeMsgAtAppStartup()
    {
        ConsoleIO.conShow("\\n");

        string borderChar = string.Concat(Enumerable.Repeat("*", 78));
        ConsoleIO.conShow("\\p" + borderChar + "\\n", "\\ge");
        ConsoleIO.conShow("\\p\\", "\\ge");

        ConsoleIO.conShow("\t\t\tHeroes", "\\ye");
        ConsoleIO.conShow("!\t\t", "\\wh");
        string spaces37 = string.Concat(Enumerable.Repeat(" ", 37));
        ConsoleIO.conShow("\\p" + spaces37 + "/\\n", "\\ge");

        ConsoleIO.conShow("\t\t...a text adventure by Bryan Mueller \\n", "\\wh");
        ConsoleIO.conShow("\t\t......email: \\n");
        ConsoleIO.conShow("\\p/", "\\ge");
        ConsoleIO.conShow("\t\t......................copyright 2017", "\\wh");
        ConsoleIO.conShow("                          \\\\n", "\\ge");
        ConsoleIO.conShow("\\p" + borderChar + "\\n", "\\ge");
        ConsoleIO.conShow("\\d", "\\wh");
    }

    private static void deleteGame()
    {
        ConsoleIO.conShow("\\pDelete game code goes here...\\n");
    }

    private static void exitGame()
    {
        ConsoleIO.conShow("\\pQuit game? ");

        int result = ConsoleIO.areYouSure();

        if (result == 1)
        {
            ConsoleIO.conShow("\\n", "\\wh");
            ConsoleIO.conShow("\\pBye-bye...");
            ConsoleIO.conShow("\\n");
            System.Threading.Thread.Sleep(1000);
            Environment.Exit(0);
        }
    }

    private static void helpGame()
    {
        ConsoleIO.conShow("\\pHelp code goes here...\\n");
    }

    private static void loadGame()
    {
        ConsoleIO.conShow("\\pLoad game code goes here...\\n");
    }

    private static void newGame()
    {
        ConsoleIO.conShow("\\pNew game code goes here...\\n");
    }

}

After ensuring that all the files you've updated or created have been saved, switched to the command prompt and enter

build

If you haven't made any errors, all will compile nicely, and you can run program heroes.exe.

Only the Exit Game menu item has been given functionality via code. Our game doesn't do much yet, but it does give a welcome message and some menu choices:

You can download the source files for this lesson by clicking here.

Your getAppDirectory() method that you added in this lesson will be crucial as we continue. The getNumberSelection() method will allow you to create menus up to nine items in length. And method createDirectory() is needed for various subdirectories we'll be creating to organize game data. And by the way, I know we could have just used the IO namespace's Directory.CreateDirectory(). But we've encapsulated it in ConsoleInputOutput.cs because directories are bound up with the file input/output we'll be doing.

I want you to take one last look at heroes.cs and take special note of the fact that we're using a loop to run our game.

Unlike in WinForms C# programming, in a console app, we don't have any easy/convenient way to do heavy event-based programming. So we use a loop for our game.

See you in Lesson 12 :D

Return to Lesson 10 | Proceed to Lesson 12

Return to Index

C# Text Adventure: Lesson 10

Filtering User-Input for "Yes" or "No"

In this lesson, we'll develop a method in ConsoleInputOutput.cs to present a question (that can be answered with "yes" or "no") and obtain a definitive "yes" or "no" answer from the user.

Why? Because many times in a text adventure game we may want to prompt the user for a yes/no response. Get confirmation of user's intention to quit the game. A dragon appears atop the hill. Do you want to run? You are carrying a lot in your pack. Would you like to sell some of it before you leave Gerzack's Emporium?

Now, perhaps you could come up with your own method to accomplish this. And perhaps it would be better than mine. If that's the case, go for it! But below I present to you method areYouSure():

    public static int areYouSure()
    {
        /*  This method loops until user either enters 'Y' for "yes" or 'N' for "No".
            It returns 1 if 'Y' was entered, 0 if 'N'. */

        Console.TreatControlCAsInput = true; //prevents ending in user presses Ctrl-C

        bool invalidInput = true;
        int result = -1;
        string response = string.Empty;

        while (invalidInput)
        {
            conShow("\\n");
            conShow("\\pAre you sure you want to quit and exit the application? (", "\\wh");
            conShow("Y", "\\ge");
            conShow(")es or (", "\\wh");
            conShow("N", "\\ge");
            conShow(")o ", "\\wh");
            conShow("", "\\ye");

            ConsoleKeyInfo cki = Console.ReadKey();
            response = cki.Key.ToString().ToUpper();

            if (response == "Y" || response == "N")
            {
                invalidInput = false;
                result = 1;
                if (response == "N") { result = 0; }
            }
        }

        conShow("", "\\ye");
        ConsoleColors.conWhiteOnBlack();
        conShow("\\n");
        return result;
    }

Copy/paste it into ConsoleInputOutput.cs and save the source code file. Then, switch to your command prompt and execute a dir *.dll command. Although the timestamps on *your* DLLs will likely differ from mine, they should be approximately the same size. Of course, if you expanded the selfTest() method in *ConsoleColors.cs*, it's DLL may be larger than the one shown in the following screen shot:

Whatever your timestamps for those two libraries, they'll bear later timestamps (and, if you saved the new method in ConsoleInputOutput.cs, its corresponding DLL will grow in size) after you enter the following at the command prompt:

rebuild

You know what happens now, right? Yes, we're going to build a test app to see if our newly added areYouSure() method works as expected. So, create a new source code file named testInputOutput3.cs.

Enter the following source code listing in a new Notepad++ file and save it as indicated above:


public sealed class testInputOutput3
{
    public static void Main()
    {
        ConsoleIO.conShow("", "\\wh");
        ConsoleIO.conShow("\\n");
        ConsoleIO.conShow("\\pYou stand with your hand hovering over the\\n");
        ConsoleIO.conShow("\\plaunch button for the nuclear ICBMs. Press \"Launch\"?\\n");

        int result = ConsoleIO.areYouSure();

        if (result == 1)
        {
            ConsoleIO.conShow("\\pBang! Hello World World III!\\d", "\\ma");
        }
        else
        {
            //zero was returned; only 1 or zero CAN be...
            ConsoleIO.conShow("\\pMmm, yeah. Discretion is the better part of valor.\\d", "\\ma");
        }
        ConsoleIO.conShow("", "\\wh");
    }

}

Just for shits and giggles, rebuild your DLLs using the following at the command prompt:

rebuild

Then, compile the test file by entering the following at the command prompt:

csc /r:ConsoleInputOutput.dll testInputOutput3.cs

Below, I show the two possible outputs of the test program, depending upon whether you answer Yes or No to the question the program poses:

Note that the areYouSure() method even poses the "Are you sure (Y)es/(N)o" question. It loops until a definitive Yes (user entered 'Y') or No (user entered 'N') is obtained. Then it returns integer 1 if "yes" or 0 if "no". A useful method!

You can click here if you need/want to download the files associated with this lesson.

Return to Lesson 9 | Proceed to Lesson 11

Return to Index

C# Text Adventure: Lesson 8

Begin our Input/Output class

Now we're going to begin building up a class of static methods that have to do with either input at the console/input from files, or output to the console/output to files.

Go to your heroes directory. If you haven't already, go ahead and move testColors2.cs to your

/testcode/

subdirectory. If the file testColors2.exe still exists, delete it. Your project directory should now look like this:

Using Notepad++, create a new C# source code file named ConsoleInputOutput.cs in your heroes project directory.

Create the skeleton or boilerplate code per the following listing:

using Sytsem;
using System.IO;

public sealed class ConsoleIO{
    public static void conShow(string p){
        /* this method builds upon that of Console.Write() by looking for
           special character sequences with string p and translating them
           variously into (a) a space prepended to the passed string p,
           (b) a newline appended to string p, or (c) two newlines appended
           to string p. Lines 13 through 16 below can be shortened to the 
           single line of code shown in line 18 below: 
           
           Console.Write(" ");
           Console.Write("This is my string.");
           Console.Writeline();
           Console.Writeline();
           
           conShow("\\pThis is my string.\\d");

           */
    }
}

Save the source file. It should look like this in your Notepad++ editor (you may have to work on the indenting a bit if you copy/paste from the above listing):

Save ConsoleInputOutput.cs We won't bother compiling it to a DLL library yet, since it doesn't yet contain any useful methods.

Create source code file testInputOutput1.cs, and enter the following:

using System;

public sealed class TestInputOutput1
{
    public static void Main()
    {
        ConsoleColors.conWhiteOnBlack();
        Console.WriteLine();
        Console.Write(" Name: ");
        ConsoleColors.conFgCol("\\ge");
        Console.WriteLine("Bryan Mueller");
        ConsoleColors.conFgCol("\\wh");
        Console.Write(" Location: ");
        ConsoleColors.conFgCol("\\ge");
        Console.WriteLine("Kentucky");
        ConsoleColors.conFgCol("\\wh");
        Console.Write(" Age: ");
        ConsoleColors.conFgCol("\\ge");
        Console.WriteLine("45");
        ConsoleColors.conWhiteOnBlack();
    }
}

Save the test file, then switch to your command prompt and compile it with a reference to the ConsoleColors.dll library, like so:

csc /r:ConsoleColors.dll testInputOutput1.cs

If you had any errors with the compilation, check the screen shot below. Does your source code file match what you see in the following screen shot?

After successful compilation, testInputOutput1.exe produces the following output:

Now, I want to draw your attention to the fact that it took us a dozen lines of code (not counting the two calls to ConsoleColors.conWhiteOnBlack()) to produce just three lines of output. That's because I've formatted the output to make it easier on the eyes. Do you disagree that the output shown in the above screen shot is more pleasant and easily readable than the unformatted version show below?

Remember that ratio: 12 lines of source to produce 3 lines of formatted output. You'll note I put a space between the left edge of the Console viewing area and the beginning of each line of text. That looks better than when the text buts up against the left edge, agree? Now let's create our first method in ConsoleInputOutput.cs, and see if we can reduce that 12:3 (i.e, 4:1) ratio of source code lines to desired formatted output lines.

Return to ConsoleInputOutput.cs in Notepad++ and let's code a static method we'll name conShow(). Here is the listing of code you'll either type or copy/paste into your source code file:

    public static void conShow(string p)
    {
        /* This method is static so that we can invoke it without first instantiating an object.
            Just pass it the string you want the Console to show the user */

        bool doubleNewline = false;
        bool newline = false;
        bool prependSpace = false;

        if (p.EndsWith("\\n"))
        {
            newline = true;
            p = p.Substring(0, p.Length - 2);
        }
        if (p.StartsWith("\\p"))
        {
            prependSpace = true;
            p = p.Substring(2, p.Length - 2);
        }
        if (p.EndsWith("\\d"))
        {
            doubleNewline = true;
            p = p.Substring(0, p.Length - 2);
        }
        if (prependSpace)
        {
            p = " " + p;
        }
        if (newline)
        {
            Console.WriteLine(p);
        }
        else
        {
            if (doubleNewline)
            {
                Console.WriteLine(p);
                Console.WriteLine("");
            }
            else
            {
                Console.Write(p);
            }
        }
    }

Here's a screen shot of the ConsoleInputOutput.cs file after saving it with its first method. I've folded some of the code:

Let's add an overload of the conShow() method, one that takes two string parameters, the first specifying the text to be shown, the second specifying what foreground color to use when displaying it.

Go back into your ConsoleInputOutput.cs source file and add the following overloaded method:

    public static void conShow(string p, string fg)
    {
        ConsoleColors.conWhiteOnBlack();
        ConsoleColors.conFgCol(fg);
        conShow(p);
    }

Save the file, then switch to the command prompt and rebuild the DLL library. Do you remember how? Like this:

csc /t:library /r:ConsoleColors.dll ConsoleInputOutput.cs

Note: don't forget to reference ConsoleColors.dll when building ConsoleInputOutput.dll

Now let us return to our test file, testInputOutput1.cs. Bring it up in its Notepad++ tab and save it as (using File→Save As) testInputOutput2.cs. We're going to modify it and see if — by referencing the ConsoleInputOutput.dll library — we can mimic our first test file's output while using fewer lines of source code.

Once you get the newly saved ConsoleInputOutput.cs file (which now contains two methods) compiled to a DLL library, modify your testInputOutput2.cs file as follows:

using System;

public sealed class TestInputOutput2
{
    public static void Main()
    {
        ConsoleIO.conShow("\\n", "\\wh");
        ConsoleIO.conShow("\\pName: ", "\\wh");
        ConsoleIO.conShow("Bryan Mueller\\n", "\\ge");
        ConsoleIO.conShow("\\pLocation: ", "\\wh");
        ConsoleIO.conShow("Kentucky\\n", "\\ge");
        ConsoleIO.conShow("\\pAge: ", "\\wh");
        ConsoleIO.conShow("45\\n", "\\ge");
        ConsoleIO.conShow("", "\\wh");
    }
}

Save this file, then go to the command line and compile it like this:

csc /r:ConsoleInputOutput.dll testInputOutput2.cs

Upon successful compilation, run the program. Voila! The second version of our test program (testInputOutput2.cs) produces identical output to the first version (testInputOutput1.cs) but uses fewer lines of source code (see below):

When we compare the relevant section of source code from each version of our test application, we see the second version achieve in 8 lines what the first version took 14 lines to accomplish.

In the next lesson, we'll learn how to take a shortcut that makes the frequent recompilation of libraries and applications less tedious at the command line.

If you became unable to build a library or compile a test app, and you're sure that you are entering the correct info at the command line compiler, the problem is likely an error in one of your source files. If you need, you can download Lesson 8 source files here and extract them into your heroes directory.

Return to Lesson 7 | Proceed to Lesson 9

Return to Index

C# Text Adventure: Lesson 7

Expanding ConsoleColors.cs

In this lesson we'll expand our existing ConsoleColors.cs source file. Currently, it contains a single static method named conWhiteOnBlack(), which sets the console's foreground color to White and background color to Black.

Open the source file in Notepad++ and let's add a static method that accepts a short string parameter and that sets a Foreground color based upon that parameter. This method will include all of the colors defined for the Console class. It's a lengthy method, so if you don't want to type it all in manually (which I still suggest you do), you can copy and paste it from the following listing:

    public static void conFgCol(string p)
    {
        switch (p)
        {
            case "\\bk":
                Console.ForegroundColor = ConsoleColor.Black;
                break;
            case "\\cy":
                Console.ForegroundColor = ConsoleColor.Cyan;
                break;
            case "\\db":
                Console.ForegroundColor = ConsoleColor.DarkBlue;
                break;
            case "\\dc":
                Console.ForegroundColor = ConsoleColor.DarkCyan;
                break;
            case "\\dy":
                Console.ForegroundColor = ConsoleColor.DarkGray;
                break;
            case "\\dg":
                Console.ForegroundColor = ConsoleColor.DarkGreen;
                break;
            case "\\dm":
                Console.ForegroundColor = ConsoleColor.DarkMagenta;
                break;
            case "\\dr":
                Console.ForegroundColor = ConsoleColor.DarkRed;
                break;
            case "\\de":
                Console.ForegroundColor = ConsoleColor.DarkYellow;
                break;
            case "\\ye":
                Console.ForegroundColor = ConsoleColor.Yellow;
                break;
            case "\\gr":
                Console.ForegroundColor = ConsoleColor.Gray;
                break;
            case "\\rd":
                Console.ForegroundColor = ConsoleColor.Red;
                break;
            case "\\ge":
                Console.ForegroundColor = ConsoleColor.Green;
                break;
            case "\\bl":
                Console.ForegroundColor = ConsoleColor.Blue;
                break;
            case "\\ma":
                Console.ForegroundColor = ConsoleColor.Magenta;
                break;
            case "\\wh":
                Console.ForegroundColor = ConsoleColor.White;
                break;
            default:
                Console.ForegroundColor = ConsoleColor.White;
                break;
        }
    }

Below, I show the source code file as it now exists. I have folded the switch statement:

Let's add one more method. One that reverses the usual white-on-black color scheme to black-on-white instead:

Save the source code file and then let's switch to the command line and rebuild our DLL from it using the following at the prompt. Note that before rebuilding the DLL, the library's size is about 3,072 bytes on disk:

csc /t:library ConsoleColors.cs

After rebuilding the library, it's size on disk is about 4,096 bytes. The DLL file's size has increased because the source code has increased.

Create a new console program to test our rebuilt library. Name it testColors2.cs and save it in our heroes project directory:

Enter source code into the test program using Notepad++, based on the following screen shot:

Save the source code for this test program and go to your command prompt. Compile using the following:

csc /r:ConsoleColors.dll testColors2.cs

At the command prompt, run the newly created executable by entering the following:

testColors2

Here's the output (note that I typed some text at the command prompt after the test program exited, just to demonstrate the effect on the console of reversing the usual foreground/background color scheme):

Modify the test program source code file as follows:

using System;

public sealed class TestCC2
{
    public static void Main()
    {
        ConsoleColors.conWhiteOnBlack();
        Console.WriteLine("I'm the usual white-on-black...");
        ConsoleColors.selfTest();
        Console.WriteLine("But press a key, and I'll change to black-on-white...");
        Console.ReadKey();
        ConsoleColors.conBlackOnWhite();
        Console.WriteLine("Now I'm black-on-white!");
        Console.WriteLine("Press any key to exit app...");
        Console.ReadKey();
        ConsoleColors.conWhiteOnBlack();
    }
}

:

Save, and recompile at the command line. Now our test program resets the console to its usual color scheme before exiting.

Now, open the ConsoleColors.cs source file, and add the following method to it:

    public static void selfTest()
    {
        conWhiteOnBlack();
        Console.WriteLine();
        Console.WriteLine("Now testing colors...");
        conFgCol("\\cy");
        Console.Write("cyan, ");
        conFgCol("\\db");
        Console.Write("dark blue, ");
        conFgCol("\\dc");
        Console.Write("dark cyan, ");
        conFgCol("\\dy");
        Console.Write("dark gray");
        Console.WriteLine();
        Console.WriteLine();
    }

Save the source file, then go to the command prompt and rebuild the DLL library using the following:

csc /t:library ConsoleColors.cs

And now, having rebuilt the DLL, go back to the test program, testColors2.cs and ensure that the source code conforms to the following listing:

using System;

public sealed class TestCC2
{
    public static void Main()
    {
        ConsoleColors.conWhiteOnBlack();
        Console.WriteLine("I'm the usual white-on-black...");
        ConsoleColors.selfTest();
        Console.WriteLine("But press a key, and I'll change to black-on-white...");
        Console.ReadKey();
        ConsoleColors.conBlackOnWhite();
        Console.WriteLine("Now I'm black-on-white!");
        Console.WriteLine("Press any key to exit app...");
        Console.ReadKey();
        ConsoleColors.conWhiteOnBlack();
    }
}

Save the testColor2.cs source code file, then return to the command prompt and compile it as follows:

csc /r:ConsoleColors.dll testColors2.cs

Run the test program at the command line. Cool, eh? On your own, expanded the selfTest() method in ConsoleColors.cs so it tests all of the cases in conFgCol(). Then rebuild the DLL, recompile the test program, and run it.

That That's it for Lesson 7. You can now move the testColor2.cs source file to your

/testcode/

subdirectory.

In lesson 8 we'll begin work on our class that will handle input and output at the console.

Return to Lesson 6 | Proceed to Lesson 8

Return to Index

C# Text Adventure: Lesson 6

Console Coloring Encapsulation

In this lesson, we are going to create a class that encapsulates coloring the output of C# console programs. Because let's be honest: if we're going to create a text adventure game, it would be darn handy if we could easily color our output.

Think about this: why might it be a good idea to put all of our console-color manipulating code in one single source file? And, likewise, why might it be wise to put all of our data input/output code in its own source file? Because that's the plan: before we actually begin coding the text adventure itself, we'll put our console coloring code into a library, and our input/output code in another library.

I don't know if you, the reader, have ever done any mid-size C# projects before (say 2-6K lines of source code), but I have, and I can attest that logically organizing related code into separate source files makes finding a particular method so much easier than if you had one giant source file. And dividing your source code into smaller files based upon topic/function their code addresses lends itself to the creation of useful libraries of code. Libraries in C# are often compiled into DLL files. In fact, DLL stands for Dynamic Link Library, a bundle of methods that can be accessed when needed by various applications.

So... to begin.

Fire up Notepad++, or whatever other editor you chose, and create a new file, saving it as ConsoleColors.cs in our heroes project folder. Be sure to Save As type "C# Source File":

After saving this (currently empty) source file, we'll create a public sealed class to hold the code we're about to create. It needs to be public so that it will be visible to code elsewhere, code that wants to utilize this class' methods. It's sealed because it's only going to contain static methods. No other classes will inherit from ConsoleColors.cs.

Now, let's add a method to this class. We'll name it conWhiteOnBlack(). Based on that name, can you guess what it's purpose will be? Yes. It will set the console to use a white-on-black "scheme", white foreground on a black background:

You may not think this method seems needed, but believe me when I say that — once we begin coding our text adventure — we'll be changing colors frequently, and will often need to reset the console to its normal white-on-black appearance.

Why note just write the following each and every time we need to do this:

Console.ForegroundColor = ConsoleColor.White;
Console.BackgroundColor = ConsoleColor.Black;

I suppose that'd be okay, if we were only going to do this occasionally. But we'll be doing it frequently, and so we can achieve a substantial savings in source-code size and our typing effort, if we can simply code the same functionality with a single line of code:

ConsoleColors.conWhiteOnBlack()

Save the file, then switch over to your command prompt. Let's create a DLL from this source code file:

At the command prompt enter the following. Be sure to type it exactly as shown below, then press Enter:

csc /t:library ConsoleColors.cs

If you don't get an error message, enter the

dir

command after building the DLL library:

As indicated by the yellow arrow in the above screen shot, our DLL library (scant as it is, currently), was created. Now let's create a console application to reference that DLL, in order to test it:

Save the above as "testColors1.cs" in the heroes directory. Now, we're going to first try compiling it without referencing the DLL that contains the

conWhiteOnBlack()

method.

The CS 103 error message issued by the compiler alerts us that our test code has no idea what we're referring to with the following line of code:

ConsoleColors.conWhiteOnBlack()

This is because we didn't reference the ConsoleColors.dll file when we compiled testColors1.cs. Let's try again, and this time we'll use the /r switch to reference our DLL library. Note the use of the referencing switch and the successful compilation shown below:

Run the newly created application, testColor1.exe, at the command line and observe the output. Our DLL has encapsulated the two calls normally needed, and a single line of code now suffices:

We're done with that particular test, so create a subdirectory in heroes named testcode. Delete the testColors1.exe file in the heroes directory and move testColors1.cs into the *testcode subdirectory. We'll be keeping any test code there, so it doesn't clutter up our main heroes directory:

Your heroes directory should now look like this:

In our next lesson, we'll expand ConsoleColors.cs, create an updated DLL from it, and test it. If you're bored so far, that's understandable. Just know that the good stuff is coming about 3-6 lessons from now, once we've done our prep work.

Return to Lesson 5 | Proceed to Lesson 7

Return to Index

C# Text Adventure: Lesson 5

Creating a Project Directory and Shortcut

In this lesson, we'll create a directory for our C# Text Adventure project, and will then create a Desktop shortcut to open it in Windows' explorer, and finally a batch file to launch a console window in our project directory.

First, let's create a directory for any command line only C# projects we may wish to work on, including the current project. We'll do this because it's a good idea to keep these command line projects separate from any Visual Studio or SharpDevelop WinForms projects.

Open your C:\ drive and create a directory named "cmdline", as shown in the picture above. If you really want to put it somewhere else, you can. But I suggest doing it here.

Now, if you failed to get Windows to recognize the C# command-line compiler by adding csc.exe to your Path environment variable in Lesson 3, there's any easy workaround. Just download and extract this copy of the compiler to your project directory, once we create it.

Within the cmdline directory, create a subdirectory named heroes. This directory will house our C# Text Adventure Game project. If you need to, download and extract a copy of csc.exe into this heroes directory now (if you successfully added csc.exe to your PATH environment variable back in lesson 3, then you should skip downloading and extraction).

Here's the project directory on my computer (C:\cmdline\heroes):

Okay, now let's create a Desktop shortcut to the heroes directory. There are plenty of tutorials on how to make shortcuts point where you wish. Google them if needed. Basically you just right-click on the Desktop, click New on the context menu, and then click "Shortcut". Ensure that it's "Target Directory" property has C:\cmdline\heroes entered:

If you create the shortcut correctly, you wind up with the following on your Desktop:

And, when clicked on, it takes you to your C:\cmdline\heroes directory. Go ahead and use the heroes shortcut to enter the project directory, then use Notepad++ to create a batch file in this directory called prompt.bat. Be sure to save the file as "All Types" rather than as ".txt":

Enter the following text into *prompt.bat" and save it:

cmd

Now go into your heroes folder and double click the saved batch file. Voila! A command prompt already pointing to our working project directory. From now on, whenever you want to follow along with the tutorials, use the desktop shortcut to open the heroes project folder, then use the batch file to launch a command prompt in that project directory. You'll use the command prompt console window to run the command-line C# compiler as this tutorial series continues:

Return to Lesson 4 | Proceed to Lesson 6

Return to Index