Hey everybody!!!
I hope you enjoyed my last Tutorial Lesson regarding the No-Frills Dungeon Crawler. Now we're going to dive into the C# fun of it all. We'll see what makes the game tick, what makes it tock, and what in the world is next in stock!
Let's get right down to it, then, and see what the Code looks like. The animated image below shows me playing the game, which isn't much of a game yet. It does show a bit of what the game will look like (yes, it's very simple and I could have done much more) and how to maneuver within the game interface.
And now for the Code...
The Grand Scale
So we've taken a look at the concept of the game in the previous post as well as the outlook for the future of the game. Now we are going to take an in-depth look at the C# Code for this game. We will get a glimpse of the overall program and then go deeper into the functions and variables and how the logic interacts.
Here you go, my fellow Nerds-in-Waiting and Chic-Geek-Knights, following is the code at a glimpse.
As you can see, quite a bit of switch logic and semi-repetitious checking. There are always a dozen ways to do the same thing, so this is just one way in many that can do the same/similar things. In essence, the switch statement is like saying "I have a bunch of conditions and want to check them each on a case-by-case scenario. Hence, you see the case statement that is part of the switch logic... you cannot use switch without it.
The whole scope may look a bit intimidating, but the logic within each segment can usually be summed up by just a few logical components. Following are the Bits & Pieces of it all.
Bits & Pieces
I'm going to go top-down through the code and explain what it all means without repeating myself over and over. I hope it all makes sense when I'm through with it all. The following sections will explain each of the parts of the code that will become the functional logic and representation to the game as a whole.
The Variables at Play
To start this section, we create an object of type Random that you might recall represents a randomly-generated number. "map" is a char based variable, which is an object that contains singular characters.
"= { ' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o' };" says to create the array and fill each with the comma-delimeted values inside the curly braces { }.
An example of the Constants to use: public const int north = 0;
Constants are variables (you remember what variables are, correct? names of things to represent something else) that stay constant and cannot be changed across time. As an example, the above line of code recognizes the variable called "north" as a public (globally-usable) const (constant) with a dataype (type of variable) of int (integer-based, therefore, numeric whole numbers). Setting "north" to a constant variable sets it to a value (this time "0") that cannot be changed.
By using constants, I can now use the word "north" to mean "0". It makes the code far easier to read when it comes to debugging and/or change. I use the directional words to describe each, that way it made the coding of movements (turning left and right) a far simpler formula of addition or subtraction from the current directional number.
"direction" is the variable that actually determines the direction that the game's character will be facing. The "map_x" and "map_y" variables are only initialized and not used more than that. In future versions I may create maps from files that this will represent the map-based coordinates; but for now, it is not used.
"north_door", "east_door", "south_door", & "west_door" are boolean (true/false) variables that I use to represent whether or not there is a doorway in the corresponding wall. As an example, if "east_door" is true it means that if you are facing the east (in the game) there will be a doorway showing, and if you are facing north, that same doorway will show up on the right-hand wall. These are used to draw the correct image.
The "backdrop" is a PictureBox component that will be used for the game-screen itself. A PictureBox is a dotNet component that can hold an image for later use. This object is contained within the structure of Visual Studio itself and, therefore, one can do a drag-and-drop through the Visual Studio interface. In this instance I just created the object within the code itself.
The Images ("left_image", "front_image", "right_image" and so on) are actual Image objects that will be used as the wall-based images. The "left_image" represents the wall image that shows only a doorway on the right-hand wall. An Image object can store the data for an image, whether from a file or some sort of data stream. In these examples (as below) the image itself will be loaded from a file in the next section.
Start & Finish
When the program begins it uses the "Form1_Load" Function to begin loading the variables and components. This is where the variables from the above section will actually become usable. That will occur behind-the-scenes prior to the below code being run.
As you can see in the image above, I use the "Image.FromFile(...)" dotNet Function in order to load the images from files. I could have drawn the images individually or dynamically, but it would have made the code harder to understand. I cannot say one way or another on whether big-name games use my technique or a dynamic technique or something completely different. All I know is that this is the way I am doing it so that it's easier to follow.
Essentially, "left_door = Image.FromFile(...);" tells us to pull the image from the file location marked by the ellipsis and load it into the Image object called "left_door". The actual image is of 3 walls with the left wall as the only one that includes a doorway in it.
The same technique is used for the other images.
"map = room_types[r.Next(0, 15)];" represents logic to randomly create a room with a variable number of walls from 1-4 based on 16 alphabetic characters. "map = " says to set the "map" variable, while "room_types[...];" says to use the char[] object, which is a char array. An array is an object type that holds multiple versions of the same variable with the same name. This one in particular is identified by the char values of the alphabet from "a" - "o" with the first as an empty space. This way "room_type[1]" would represent the 2nd value in the array (these are 0-indexed and, therefore, starts at [0]).
Arrays can be confusing, so don't be alarmed if you still need to learn more about them.
After the "Form1_Load(...)" Function you can see the "Done_Click()" Function that represents the action of clicking the Button that I called "Done" (as in the below image):
(Done Button is circled in Red and labeled with "Quit")
The code itself shows "this.Close();" which is a call to the base "Close()" Function that closes down "this" form. this is a keyword that represents the main Form/object.
Movin' & Groovin'
In order to allow you to move forward through a doorway or backward through a doorway, if there is a doorway in that direction, I have 2 buttons that look like and
.
In the above code, "MoveForward_Click()" is the Function linked to the button called "MoveForward". The logic occurs when that button is clicked (hence the "_Click" portion of the Function name.
switch (direction)
{
case north: if (CheckDirection(true)) { map_y++; LastMessage.Text = "You moved forward."; map = room_types[r.Next(0, 15)]; } else { LastMessage.Text = "You cannot go that way. No Door."; }; break;
case east: if (CheckDirection(true)) { map_x++; LastMessage.Text = "You moved forward."; map = room_types[r.Next(0, 15)]; } else { LastMessage.Text = "You cannot go that way. No Door."; }; break;
case south: if (CheckDirection(true)) { map_y--; LastMessage.Text = "You moved forward."; map = room_types[r.Next(0, 15)]; } else { LastMessage.Text = "You cannot go that way. No Door."; }; break;
case west: if (CheckDirection(true)) { map_x--; LastMessage.Text = "You moved forward."; map = room_types[r.Next(0, 15)]; } else { LastMessage.Text = "You cannot go that way. No Door."; }; break;
default: break;
}
GetDoors(map);
this.Update();
The switch command uses the variable inside the parantheses () to check different scenarios. Each scenario is prefaced by a "case ?: " where the "?" represents the value (in these cases the constants.
So, if "direction" is equal to "north" (0) then it executes a function called "CheckDirection(...)" to see if it returns as true or false (not to be confused by the parameter inside the parentheses). If that, in turn, returns as true then it changes the text value of a label called "LastMessage" to show "You moved Forward.", otherwise if it returns as false it executes the else clause. The else is the alternative of an if statement's _true" clause... "if ... else" represents the question of "if this then do this else do that". If the else clause runs, the label shows "You cannot go that way. No Door." as its text value.
The "MoveBackward_Click(...)" Function does the same of the "MoveBackward" Button object.
GetDoors(map);
this.Update();
The first calls a Function called "GetDoors" and passes in the "map" variable, the one that represents the number of doors in the room. The second line calles "this.Update();" which just refreshes and repaints this form.
Turnaround, every now and then I get a little bit lonely
Ok, I don't get lonely, but it does run the formulas to turn a different direction. The code is fairly short, actually, as can be seen here:
private void TurnRight_Click(object sender, EventArgs e)
{
direction++;
if (direction > west) { direction = north; }
GetDoors(map);
LastMessage.Text = "Turn Right.";
this.Update();
}
private void TurnLeft_Click(object sender, EventArgs e)
{
direction--;
if (direction < north) { direction = west; }
GetDoors(map);
LastMessage.Text = "Turn Left.";
this.Update();
}
If you recall, the "direction" variable is an integer number that represents the direction that the character is facing in the game. so if you turn to the right, it goes from the current direction to the next direction in line (eg. north->east... which is north (0) to east (1)). To do a right turn, all that needs to happen is the increase the "direction" by 1. "direction++;" means to do just that. 0 becomes 1, 1 becomes 2, and so on. The problem, however, is that there are only 4 directions (0 thru 3), so we need to check and reset if needed. The following line does that:
"if (direction > west) { direction = north; }"
if the expression inside the parentheses evaluates to true (eg. if "direction" is now greater than 3 (west)) then it executes the logic in the braces { direction = north; }. Doing this sets "direction" equal to "north" (0).
GetDoors(map);
LastMessage.Text = "Turn Left.";
this.Update();
This set of lines says Execute the GetDoors(map) function, then set the text value of the LastMessage label to "Turn Left." and finally to refresh and repaint this form.
To turn left, it just works in reverse.
What Direction Your Direction is in?
private bool CheckDirection(bool forward)
{
bool result = false;
if (forward)
{
if (direction == north)
{
return north_door;
}
else if (direction == east)
{
return east_door;
}
else if (direction == south)
{
return south_door;
}
else if (direction == west)
{
return west_door;
}
}
else
{
if (direction == north)
{
return south_door;
}
else if (direction == east)
{
return west_door;
}
else if (direction == south)
{
return north_door;
}
else if (direction == west)
{
return east_door;
}
}
return result;
}
The above code basically checks the direction and, if you are moving forward, it checks to see if there's a doorway in that direction. The "forward" variable is the response to "which way are you moving... forward or backward?". Therefore, if "forward" is true, then you're moving forward and, otherwise, you're not moving forward (you're moving backward).
Then, after that check, it checks the appropriate wall to see if there's a doorway in the wall.
if (forward)
{
if (direction == north)
{
return north_door;
}
This is the first check in the "forward" option. "if (direction == north)" says if "direction" equals "north" then do this. The double-equals (==) is a comparison logical expression. After that, if the "direction" is "north" then return the boolean (true/false) value of "north_door". This returns back to the code in the Movin' & Groovin' section above.
Doorways, Doorways, everywhere
Where have all the Doorways Gone?
This is the logic that determines the doorways and directions for a given room. The code below is a snippet of this Function when the "room_identifier" parameter that is passed in is an 'a'. The letter 'a' represents a door only in the north-facing wall. Therefore, if I check the "direction" value and I'm facing north, the doorway should be right in front of me only... this condition is represented by the "front_image" Image variable. You will see this case scenario below where it says "case north: ...".
private void GetDoors(char room_identifier)
{
switch (room_identifier)
{
case 'a':
north_door = true; east_door = false; south_door = false; west_door = false;
switch (direction)
{
case north: this.backdrop.Image = front_image; break;
case east: this.backdrop.Image = left_image; break;
case south: this.backdrop.Image = none_image; break;
case west: this.backdrop.Image = right_image; break;
default: break;
}
break;
The other logic is the same type of logic ("Which walls have doors? and what direction am I facing?") to show which doorways to draw.
Lessons Learned
Here are the links to your lessons learned (or not learned, if you need to go back and learn them):
C# Programming Beginner Tutorial: Basic Concepts and Ideas
C# Programming Beginner Tutorial: A First look at actual code using D&D as the Project
C# Programming Beginner Tutorial: Variables & Data Types to Fuel the Gaming Engine!
C# Programming Beginner Tutorial: Designing the Game with Programming Logic in Mind (Part 1)
C# Programming Beginner Tutorial: Designing the Game with Programming Logic in Mind (Part 2)
C# Programming Beginner Tutorial: Designing the Game with Programming Logic in Mind (Part 3)
C-Sharp Programming Beginner Tutorial: Designing the Game with Programming Logic in Mind (Part 4)
C-Sharp Programming Beginner Tutorial: Rock! Paper! Scissors!
C-Sharp Programming Beginner Tutorial: No-Frills Dungeon Crawler (Part 1)