Lesson Objectives
At the end of this lesson, you will know…
- How to plan a function
- How to write the code/logic for a function
- Some of the most common C# commands
Ok, we’ve spent all this time building up the pieces we need for our game. Now it’s time to actually get the game to do something. The first thing we’re going to add the code to handle when the player moves to a new location.
By the way, this is a long lesson, and covers a lot of new things. So make sure you have some time to complete it.
Outline of the function
The first thing we need to do is figure out what’s going to happen during a move.
When I write a function that needs to do a lot of things, like this one, I like to do some planning first – so I don’t miss anything.
When the player moves to a new location, we’ll completely heal them, we’ll check to see if the location has any quests (and if the player can complete them), and if there are any monsters to fight there.
Here is an outline of the logic for a player move function. Something like this is often called “pseudo-code”. It isn’t C# code, but it represents what the code will do.
Each indentation level is where we handle a different condition – for example, the steps to follow if the location has a monster, and the steps to follow if the location doesn’t have a monster.
- If the location has an item required to enter it
- If the player does not have the item
- Display message
- Don’t let the player move here (stop processing the move)
- If the player does not have the item
- Update the player’s current location
- Display location name and description
- Show/hide the available movement buttons
- Completely heal the player (we assume they rested/healed while moving)
- Update hit points display in UI
- Does the location have a quest?
- If so, does the player already have the quest?
- If so, is the quest already completed?
- If not, does the player have the items to complete the quest?
- If so, complete the quest
- Display messages
- Remove quest completion items from inventory
- Give quest rewards
- Mark player’s quest as completed
- If so, complete the quest
- If not, does the player have the items to complete the quest?
- If not, give the player the quest
- Display message
- Add quest to player quest list
- If so, is the quest already completed?
- If so, does the player already have the quest?
- Is there a monster at the location?
- If so,
- Display message
- Spawn new monster to fight
- Display combat comboboxes and buttons
- Repopulate comboboxes, in case inventory changed
- If not
- Hide combat comboboxes and buttons
- If so,
- Refresh the player’s inventory in the UI – in case it changed
- Refresh the player’s quest list in the UI – in case it changed
- Refresh the cboWeapons ComboBox in the UI
- Refresh the cboPotions ComboBox in the UI
Creating a shared function
We have four different functions for movement, one for each direction. And we need to do the steps shown above for moving in each direction.
We could do that by writing the same code in each function. But then, if we ever want to change that logic, we’d need to make the change in four places – and that often leads to mistakes.
So, we’re going to create a new shared “MoveTo” function to handle movement to any location. Each of the four movement functions will call that one new function.
Storing the player’s location
We also need a place to save the player’s current location. Since this value will change, we need to store it in either a variable or a property.
In this case, we’ll make a property in the Player class. It makes sense to do this because a player’s location is a “property” (in the general sense) of the player.
Storing the current monster
We also need a place to store the current monster that the player is fighting, in case they move to a location that has a monster there.
In the World class, we have a list of all the monsters in the game. However, we can’t use the monsters from there to fight against. We only have one “instance” of each monster. So, if we fought against the rat in the World.Monsters list, and killed it, the next time we fight against it, it would already be dead.
When we move to a new location, if it has a monster there, we’ll create a new instance of that type of monster and save it to a variable. Then, the player will fight against that instance of the monster.
Now that we know what we want to accomplish, we’re ready to add the code.
Creating the functions to move the player
Step 1: Start Visual Studio and open the solution.
Step 2: First, let’s create the new property to store the player’s current location.
Double-click on the Player class, in the Engine project. Add a new property named CurrentLocation, with a datatype of Location.
public Location CurrentLocation { get; set; }
Note: You don’t have to put the properties in any specific order. I just like to like to keep my List properties at the end of the properties. I find it a little easier to read when they’re grouped in the same place in every class.
Step 3: Right-click on the SuperAdventure.cs form, in the SuperAdventure project, then select “View Code”.
Step 4: Because we have a lot of code to write, and it’s easy to mistype something, copy the new code for the SuperAdventure form from here (https://gist.github.com/ScottLilly/208630cfcdded1cbfdc0) and overwrite all the existing code in SuperAdventure.cs with it.
What’s in the code you just added?
On line 18, we added a new variable to hold the monster that the player is fighting at the current location.
In the form’s constructor, we do a couple things to start the game.
On line 25, we “move” the player to their home. Since the MoveTo function expects a location as the parameter, we need to use the World.GetLocationByID() function to get the correct location. This is where we use the constant World.LOCATION_ID_HOME, instead of using the value 1. It’s much easier to look at the code with the constant and know what it’s supposed to be doing when we use this clearly-named constant.
On line 26, we add an item to the player’s inventory – a rusty sword. They’ll need something to fight with when they encounter their first monster.
We added the new MoveTo function to handle all player movement.
We’ve also gone into each of the four functions that handle moving in a different direction and had them call the MoveTo function.
The MoveTo function
The first thing you may notice in this function are the lines that have a “//” in them. These are comments. Comments are ignored by the computer. They only exist for programmers to read, to know what the program is supposed to be doing. Everything after the double-slashes, until the end of the line, is ignored by the computer.
I used a lot more comments in this function than I normally would. That’s to make it easier for you to follow along with what is happening, and see how the code ties back to the pseudo-code we have above.
The second thing you may have noticed is that this function is long – over 300 lines long.
That’s really too long for a function.
When a function is that long, it’s difficult to keep track of what exactly it’s supposed to be doing. But we’re going to clean this up in the next lesson. Personally, I like to keep my functions around 10 to 40 lines long.
We’ll break this function into smaller functions in the next lesson.
What’s happening in the MoveTo() function?
On line 57, we have our first “if” statement.
In this case, we check if the new location has any items required to enter it.
The “!=” is how C# says “not equal to”. The exclamation point, when doing any sort of comparison in C#, means “not”. And “null” means nothing/empty.
So, if the ItemRequiredToEnter property of the location is not empty, we need to check if the player has the required item in their inventory. If it is empty, we don’t need to do anything – there is no required item, so the player can always move to the new location.
On line 72 we see if we found the required item in the player’s inventory. If we didn’t find an item with a matching ID, the “playerHasRequiredItem” variable will still be “false”.
Notice the exclamation point in front of playerHasRequiredItem.
Let’s assume the player does not have the required item in their inventory. The “playerHasRequiredItem” variable will have a value of “false”. Doing a “not” on a Boolean variable, reverses its value: “!true” equals “false”, and “!false” equals “true”.
Thinking “if not false” is not as clear as thinking “if true”. But they both mean the same thing.
On line 75, we display the message that the player is missing the item required to enter this location. This line has a new “+=” symbol.
“+=” means, take the value from the variable/property on the left, add the value on the right to it, and assign the results back into the variable/property on the left.
When you use “+=” with a string, it means, “add the string value on the right to the end of the existing string”. When you use it with a number, it means, “add the value on the right to the value on the left”.
Here, we take the text in the rtbMessages RichTextBox, and add our new message to the end of it. That way, the player can still see the old messages. If we used the “=” sign instead, it would replace the existing “Text” value with our new message.
We also have “Environment.NewLine”. This adds an “Enter” to the text, so the next thing we add to it will be displayed on the next line, instead of the end of the current line.
Line 76 has “return”. This means “exit out of this function”.
Since this function is a “void” function (see line 54), it doesn’t return a value. We can “return” here and not do the rest of the function. We want to do that in this case, because the player does not have the item required to enter the location. So, we don’t want to do the rest of the function, which would actually move them to the location.
On lines 84 through 87, we make the movement buttons visible, or not, based on whether or not the new location has a place to move to in each direction. We do this by checking if the property for the location is empty or not.
The “Visible” property of the buttons expects a Boolean value: true or false.
So, on line 84, if the LocationToNorth property is not empty, the value to the right of the equal sign will evaluate to “true”, and the button will be visible. If the LocationToNorth property is empty, this will evaluate to “false”, and the button will not be visible.
On line 100, we check if there is a quest at this location. If so, we need to do some more work.
Lines 106 through 117 are where we look through the player’s quest list, to see if they already have the quest at this location and if they already completed it.
Lines 128 through 163 looks at each item required to complete the quest, then checks each item in the player’s inventory, to see if they have it, and have enough of it, to complete the quest.
There are some “break” statements in this “foreach”. Those are used to stop looping through the items and exit the “foreach” loop. If we discover that the player doesn’t have one item, or enough of it, to complete the quest, we can stop checking for any other items.
Line 180 has a “-=”. The “+=” means, “add the value on the right to the variable/property on the left”. So, a “-=” means, “subtract the value on the right from the variable/property on the left”. You can only use this with numbers, and not strings, unlike the “+=”.
In this case, we are using it to remove items from the player’s inventory that they turn in to complete the quest.
On line 204, there is a “++”. When you have this after a variable or property, it means “add 1 to this variable or property”. There is also a “–“, for when you want to subtract 1 from a variable or property.
At lines 264 through 273, we create the new monster to fight, by making a new monster object, using the values from the standard monster in our World class.
Updating the DataGridViews in the UI
From lines 290 through 321 we update the DataGridView controls in the UI.
The player’s inventory will have changed if they completed a quest. The items needed to turn in were removed from their inventory. The reward item was added to their inventory. So, we need to update the UI with their current inventory.
Also, if they received a new quest, or completed an existing one, their quest list would change.
Updating the ComboBoxes in the UI
For the ComboBoxes in the UI, we create new lists to hold the specific datatype of items we want to show in the list (lines 324 and 353). Next, we go through all the items in the player’s inventory and add them to these lists, if they are the correct datatype (lines 326-335 and 355-364).
On lines 328 and 357, there is a new comparison: “is”. This is used to see if an object is a specific datatype.
Remember how we created the Weapon and HealingPotion sub-classes of the Item class? When you create a Weapon object, its datatype is both Weapon and Item. When you create a HealingPotion object, its datatype is both HealingPotion and Item.
If the lists are empty (weapons.Count or healingPotions.Count are equal to 0), we hide the ComboBox and “use” buttons, since there is no weapon or potion for the player to use.
If the lists have items in them, we “bind” the list to the comboboxes (lines 345-349 and 374-378). The “DisplayMember” determines what property will be displayed in the comboboxes. In this case, we want to display the value of the Name property. The “ValueMember” is the behind-the-scenes value we’ll use a little later, to know which item was selected.
NOTE: There are better ways to connect your list properties to DataGridViews and ComboBoxes. But we’re just concentrating on the basics in these tutorials.
NOTE: Hopefully you noticed something about the variable names. They are generally long and descriptive. That makes it easy to understand what values they are supposed to hold. By making your variable names descriptive, it will be easier to work with your program – especially when fixing bugs or making changes in the future.
There are a couple cases in the “foreach”s where I use short variables like “qci” and “ii”.
Since the foreach loop is only a few lines long, I sometimes use a shorter variable name. The variable has a very short life – it only exists within those few lines of the loop. You can display the whole loop on your screen, without scrolling. So, it’s easy to keep track of where the variable is populated and where it is used. If the loop was longer, I’d use a longer, more descriptive name.
Summary
Now you’ve seen how to plan out your logic in pseudo-code, and then create a function in the program to perform that logic.
You’ve seen how “if”s, “else”s, and “foreach”s are used in a function.
You’ve also seen what a huge function looks like, and you probably have an idea how difficult it would be to work with it. The next lesson will cover how to clean up that function and make it easier to understand.
We covered a lot in this lesson. If there was anything that wasn’t clear, please leave a comment below and I can update this lesson with some more details.
Source code for this lesson
Next lesson: Lesson 16.2 – Refactoring the player movement function
Previous lesson: Lesson 15.1 – Getting random numbers for the game
All lessons: Learn C# by Building a Simple RPG Index
Hi Scott, thanks very much for this tutorial! I’m running into some problems here, however. The game runs, and it only gives me the choice to go where there is a location, as planned. I can also receive quests. However, I never encounter any monsters, and if I move somewhere I’m not supposed to go yet, the game crashes. I also can’t see the Rusty Sword in my inventory, and I have never been able to click “use”. If I move north a couple of times and then try to return south, it also crashes. Each time, the trouble seems to be to do with a “System.NullReferenceException”. “MTEngine.InventoryItem.Details.get returned null”. Do you have any idea how to solve these problems?
You’re welcome. Can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it?
Hi Scott. I think this should contain all you need: LINK REMOVED FOR PRIVACY
I downloaded the files, but I don’t see the MiningTown.csproj file, or the Windows Form file for the UI. Can you check the upload and let me know if you find them?
Sorry about that. I uploaded the complete folder to the link now: LINK REMOVED FOR PRIVACY
I think I found the problems.
The InventoryItem class was not setting the Details and Quantity properties in its constructor. Instead, it was setting two other variables “item” and “v” (which are never used in the program). So, when the program tried to look at the player’s inventory, the Details property was null – which caused an error.
For the locations, the btnWest_Click and btnEast_Click functions were moving to the opposite directions (the “West” button moved the player East, and the “East” button moved them West). Just change the parameter passed into MoveTo, and it should work.
Here is a link to the updated code: https://gist.github.com/ScottLilly/fa612826c933302b3c7b4aedab5dca19. Let me know if those do not fix the problems, or if you have any other questions.
Thank you very much for your help! Yes, this has solved my problems 🙂 Thanks for taking the time to look all of this over. It’s an amazing help
Hey man I get error when I build or run the game. I am able to travel North two times and when I encounter a location with a quest I get error point to Engine.PlayerQuest.get returned null. VS marks this line: foreach (PlayerQuest playerQuest in _player.Quests) {
dgvQuests.Rows.Add(new[] { playerQuest.Details.Name, playerQuest.IsCompleted.ToString() }); }
I’ve so far searched through these lessons to see if I missed anything and not found anything. Is it possible to see a copy of your PlayerQuest.cs I guess I might have missed something there.
Thanks for taking the time to look over this for me.
The last change to PlayerQuest.cs was in lesson 10.1.
If that does not help you fix the problem, can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it?
Of course, can you take a look at this: LINK REMOVED FOR PRIVACY
It looks like the problem is with the PlayerQuest class. Yours has an extra constructor that is not setting the Details property. When the datagrid tries to get the quest name from the Details property, it has the “null reference exception” error – because Details is null.
Check your PlayerQuest with the one in Lesson 10.1.
Let me know if that does not fix the error.
Yea I had a feeling something was off there. I got it to work now! T
hanks for the help man! Much appreciated!
You’re welcome!
Hey I keep getting “The name _currentMonster does not exist in the current context.”
Can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it?
Hello, I am using Visual Studio 2017. I have noticed multiple times through the lessons my SuperAdventure.cs file has a private method called SuperAdventure_Load that I don’t see in your source code for any of the lessons. I have tried removing it and always get an error saying: “Error CS1061 ‘SuperAdventure’ does not contain a definition for ‘SuperAdventure_Load’ and no accessible extension method ‘SuperAdventure_Load’ accepting a first argument of type ‘SuperAdventure’ could be found (are you missing a using directive or an assembly reference?)”. So I have been working around it and leaving it in place but in this lesson if I copy the source code and put it above SuperAdventure_Load I get errors when trying to run or build the program. If I put it after SuperAdventure_Load the program will run but the buttons do not work so I’m stuck on the home location screen. any help would be much appreciated!
This probably happens if you accidentally double-click on the SuperAdventure form, while in the Design mode screen. Visual Studio will create an “eventhandler” in SuperAdventure.Designer.cs that points to a function it creates in SuperAdventure.cs. But, when you paste in the code from the lesson, it doesn’t have the extra function.
You can fix this by editing SuperAdventure.Designer.cs. The error message should tell you the line where the eventhandler is. You can delete, or comment out, that line.
There is more information about eventhandlers in Lesson 21.3.
Let me know if you try that and it doesn’t work.
Hi, I have a problem with Program.cs line 19 Application.Run(new SuperAdventure()); ‘SuperAdventure’ is a namespace but is used like a type Im not sure why its happening
When you created your projects, did you rename one of them? If so, you might be able to fix the problem with the information here: https://www.scottlilly.com/learn-c-by-building-a-simple-rpg-index/lesson-02-2b-fixing-the-namespace/.
If that isn’t the problem, can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it?
Hi, sorry if I’m sending this a second time I thought I sent it but I cant see it. I did 2.2b and am still getting the error this is the link LINK REMOVED FOR PRIVACY I’ve never posted anything to github before so I hope I did it right, Thanks for the help.
The Engine project wasn’t in the GitHub repository. Can you upload it and let me know when it’s there?
Thanks,
Scott
I added it sorry I didn’t realize that the engine was in a different folder from the game LINK REMOVED FOR PRIVACY
In your game project (the UI project), edit Program.cs and change it so it matches the code here: https://gist.github.com/ScottLilly/ab356b42d5f4ded8dd02243590825025
That’s the class that is run when the program starts up. Because your project has a different name, you need to call the SuperAdventure class by calling SuperAdventure.SuperAdventure(), so the program knows which namespace to find the SuperAdventure form/class.
Let me know if that doesn’t work for you. I can upload a fixed version for you, if you need one.
That worked thanks so much for the help :).
Hi Scott,
I’m having a bit of trouble compiling the super adventure code that was provided above. I am getting a CS1061 error ‘Location’ does not contain a definition for ‘LocationToWest’ and no accessable extention method ‘LocationToWest’ accepting a first argument of time ‘Location’ could be found (are you missing a using directive or an assembly reference?).
I believe I am missing definitions for the LocationToWest (and the other 3 directions) definitions in the location class. I could potentially just write them in myself, but since I’m a beginner, I don’t want to make a mess. Did I miss something in the tutorial? I have searched and I haven’t found anything.
Hi Joshua,
Double-check your code with the code from lesson 09.1. That was where we added the LocationToWest property (and other directions).
If that doesn’t solve the problem, can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it?
so when i try to start the thing right now which i think i saw somebody in the other comments do, it gives me 3 different errors, “The name ‘rtbMessages’ does not exist in the current context”, “The name ‘dgvInventory’ does not exist in the current context”, and “The name ‘dgvQuests’ does not exist in the current context”
It sounds like the controls (rich text box and datagrids) don’t have the same names as what the code is looking for. Check the changes from lesson 12.1 and make sure you have all the controls listed and that they have the exact names listed (including the same upper and lower case letters). C# is case-sensitive, so “rtbmessages” (lower-case “m”) is not the same as “rtbMessages” (upper-case “M”).
Let me know if that doesn’t fix the problem.
nope, that sure didn’t fix the problem, as the names were already correct, except for “rtbMessages”, which i have no idea how i missed seeing as the other one was fine. the problem seems to be specific to the data grid views.
Can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it?
i guess imma just https://github.com/SpiffSpaff/Thing
not really sure on how to upload to github, hopefully this works, first time i used it
The Engine files are there, but I don’t see the UI files or the SuperAdventure.sln file. It might be easiest to compress the files and directories into a “zip” file and upload that.
well. i did that, dont know how to remove the old one, so uhhhhh
OK. I got the files.
Double-check the names of the datagrids in the SuperAdventure form’s design mode. I see them starting with “dvg”, when they should start with “dgv” (the “v” and “g” are reversed).
absolute brainlet moment from me, thanks
Hey Scott!
First off I would like to thank you for this wonderful tutorial, as one who has no knowledge of the C# language this was certainly a fun was to be introduced to it. As of right now, I do believe I have (mostly) all the correct code in the correct placed regarding the SuperAdventure solution (this is before I begin to place my own spin on the game), however, whenever I start the game, the UI appears to be unresponsive as even the “North” button dead. At the moment, I have tried to figure out many ways in which this occurred, but came up blank. If you could possibly diagnose my problem that would be very appreciated!
Attached to this comment is the github link to my code for both SuperAdventure.cs and SuperAdventure.Designer.cs, let me know what you think!
https://github.com/tylerzyinski/SuperAdventure.cs.git
Anyhow, Have a Great Day!
Hi Tyler,
I’m sorry it took me so long to respond. I moved to a new apartment and was having problems with the Internet.
The most-likely problem is that the buttons are not connected to the movement functions. This is done by “eventhandlers” – they handle an event, in this case, the button’s click event. You can read about them in lesson 21.3.
Or, you can open the SuperAdventure form in Design mode, and single-click on each of the direction buttons (be careful not to double-click on them, or Visual Studio will create its own event handlers). Look in the “Properties” tab of each button and set it “Click” event to its movement function. It should look like this:
Let me know if you try that and it doesn’t work.
Hello Scott,
for some reason when i write _player just after the moveTo function in the scope of public partial class SuperAdventure : Form. it say it doesn’t exist in this context i check my thing all my class are public. then in SuperAdventure i have private Player _player;
public partial class SuperAdventure : Form
{
private Player _player;
private Monster _currentMonster;
Hello,
Can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it?
I Found why it wasn’t working 🙂 thank you
private void MoveTo(Location newLocation)
{
//Does the location have any required items
if(newLocation.ItemRequiredToEnter != null)
{
// See if the player has the required item in their inventory
bool playerHasRequiredItem = false;
foreach(InventoryItem ii in _player.Inventory)
{
if(ii.Details.ID == newLocation.ItemRequiredToEnter.ID)
{
// We found the required item
playerHasRequiredItem = true;
break; // Exit out of the foreach loop
}
}
if(!playerHasRequiredItem)
{
// We didn’t find the required item in their inventory, so display a message and stop trying to move
rtbMessages.Text += “You must have a ” + newLocation.ItemRequiredToEnter.Name + ” to enter this location.” + Environment.NewLine;
return;
}
}
// Update the player’s current location
_player.CurrentLocation = newLocation;
I’m trying to better understand what’s happening here. When we find that the player has the required item, the bool value changes to true, and we break out of the foreach loop. So the bool acts as a constant value within the if statement, and the foreach statement essentially defines the “if” statement. Once we break out of the foreach loop the computers next process is line 81 to update to our new location.
Alternatively when we fail to break the loop and “return” from my understanding either stops the function and location information reverts to it’s state before we processed line 54 (or doesn’t change), or we return to line 54 and since it’s void, the function does not get processed upon return and can only be processed from input.
Let me know if I’m on it or way off, my main confusion was how exactly the break function works.
Hi Josh,
If we find a matching item, we break our of the loop (line 68). The next line that is proccesed is line 72, not line 82. The loop ends at line 70, and we are only breaking out of the loop from lines 62-70, not the “if” statement from 57-78. “break” only gets you out of a “for” or “foreach” loop, not an “if”.
If the item is not found, “playerHasRequiredItem” will be “false”, so we will go into the code block on line 74. That will display the message that the player does not have the required item. Then, on line 76, the “return” will cause us to completely exit out of the function – not executing any further lines.
If the item is found, the program will not run the code block at line 74. It will go to the code at line 81, which sets the current location and does everything else related to moving the player to a new location (set the allowed movement buttons, maybe give the player a quest, maybe instantiate a monster to fight).
Does that make sense?