Loading
Lesson 05
Courses / Build a Game with SDL - Make a Labyrinth with C++ from Scratch - Beginner Tutorial
Improving the Codebase with Refactoring using Game class for Better Management

Video Transcript

This lesson will be dedicated to improving the codebase so it is better manageable. As we have right now we have a file main.cpp and pretty much all the code is written inside this file. What we want to do is isolate all the logic outside the main function. We can get out the logic outside the file main.cpp. The ultimate goal for a game would be to modularize every single part of the game into individual files that each have a very well-defined function, a specific responsibility. If you want to think in that way you can look up the single responsibility principle. That's a good start. Since there are so many things we could modularize we are actually just going to do one thing in this lesson. And that is I am going to isolate all the logic. All I want to do is remove the logic from main and move it to another class that I will call the game class. The game class will pretty much have all the code that we have right now and it will be referenced from the main function. But before we do that I want to improve the game map. Because the game map doesn't really look like a maze and there is not many blocks that are blocking the player from the entrance to the exit of the library. So I went ahead and actually changed the field zeros to ones because if you remember from the second line onwards they represent the tiles. Another means it's a floor tile. One means it's a wall tile. So when I had and modified this is the before of the very first several first lines I replaced with these. That's the difference. It's in green. And I added a few walls so that the player can go from the entrance to the exit and there is something blocking them on the sides. You can do your own. You can make your own map. You can do it by hand by changing the zero to ones or the ones to zero if you want to make a floor. Of course ideally in the production environment out there you would have a map editor. Map editor you could visually change the tiles and see it in real time how it looks like. Let's go through this lesson and we can just do it manually. So let's see what it looks like after the change that I made. G++ main.cpp-lstl2.a.out. So this is what it looks like. Here's my game screen on the bottom left. You can see there are wall tiles in white. I reached the end right here. The red tile. So that's it. So let's start the refactoring of the code. Let's improve the code base. So see everything is in main right now. So let's create a new file. Call it game.h. This is going to be the header file. So we're going to have two files right for the game class. We're going to have game.h that will set some sort of contract for what the game class will have. And we're going to have game.cpp that will have all the implementations for each of those contract clauses let's say. So game.h is the blow print for the class. All the signatures we're going to have attributes for the and we're going to have the member functions. Let's go back to the header file. So let's declare a class game. That's open brace and at the end after the closing brace put a semicolon. That's the syntax. Name of the class is game. The key word is class here. Now by default in C++ anything that goes here will be under the private school. That means if you try to call a member function to try to access the attribute from the outside right you instantiate to create a new object based on this class game. And if you try to access that private member it will not work right. So you have to make it public and that's with the explicit keyword public. So I'm going to write public everything that goes after public here under public will be public everything before be implicitly private. Okay. Let's save that. Going back to main.cpp let's see what we have here that we can isolate. We have main it's called it's initializing SDL and at the end of me it's cleaning up and quitting SDL. So we have some two basic operations here. First you initialize something. You initialize the game meaning you're going to do some stuff. You're going to load some stuff set up the systems etc. So we could make a function for that and we can call it initialize or init or whatever you want to call it something start up to you choose the name. And at the end we have this operation of cleaning up and closing all the systems that were open and that can be called the shutdown operation or the closing operation whatever you want to call it something to do similar to the word close or shut down or whatever. Okay. So let's start by doing that. So I'm going to take this see out and the SDL init this part. So I'm going to first I'm going to copy so we have it lingering there and I'm going to remove later. I'm going to copy lines 173 and 178. Now I want to put it in game about CPP that where we'll have the implementation but I need to put this somewhere. I'm going to call a function. Okay. Let's call this function initialize. This function will not return anything. So void is the return type. Inside initialize. I'm going to put the logic to see out starting and then initialize the SDL init video. Now I want initialize to be part of the game class. How can I make it a member function of the game class? You can prepend it with the name of the class colon colon. Now this thing. Okay. I want to declare that in the header files. We go game.h. I want to call it void initialize here under the public line for. So this is the header file for game.h. We declare the initialize function here and then we can define its implementation in the .cpp file. Now in order to get the signature we have to include, so pound include game.h so that it gets that blueprint for the class and we can associate the implementation to the initialize member function. Now since we're using here, we're using standard cout. We need to also include that. Remember the cout comes from IO stream. So I'm going to include that. Now for the next one we're calling sdl init. This function is from the sdl.h header file. So we need to include that as well. So pound include sdl2 slash sdl.h. That's the way it works for my system. I don't know if your system will have the same include path to get to sdl. And that's the same thing we had in main.cpp. By putting in angle brackets that works as well. Let me put it either way should work. Let me just follow what I had before. So sdl log also part of this header and this too. So that's it for initialize. Now that we have this, we can go back to main, back to the main function and replace the code that we copied with the call to game initialize. Now we have to create an object, an instance of the game class. So I can just do game and I call any name you want for the variable. I'm just going to call a game in lowercase. It's almost similar to the class name. And that will instantiate an object of the game class. With this object I am going to go call game.initialize like that. And that will call that function. Because I'm referencing this type called game, it doesn't know about it from the main.cpp file. So we have to include game.h. Okay so go all the way up in main.cpp. Now we're going to include game.h. So it knows about the game type. Now let's test it out to see if I didn't miss anything. So let's recompile. And it's giving out a problem right because I only recompile with main.cpp. It doesn't know, under find reference to game, call to initialize. So it doesn't know about game. So I have to add to the list of files that I'm going to compile. So in addition to main.cpp I also have to pass game.cpp. And because the header is already included from game.cpp that we don't have to specify here. And don't forget the link. And now we have a member function for game initialize. Return statement value. And function return void. Ah okay I see what's going on. Back to game.cpp implementation for initialize. We're returning one here. This was meant to return from the main function. Now you could change this to return to value or not or you could use the exit statement. Some sort of function that can abort the program and stop it right away. That would be one way of not having to return from here. Okay especially if you have very, like if you have modules that are deep within the code base and there's no way you can return from main with one level deep. Maybe it's several levels deep and you would have to return the bull all the way up to the top. So maybe you want to do something else here. You could use exit with the code. Let me see that. If that works. Okay. Seems to be working fine. I want to simulate the error so I want to show you if it exits. So instead of not equal to zero I'm going to place actually I'm going to place greater than or equal to zero. Try to simulate the error to see if you can see it is starting and it calls info fail to initialize SDL and get error. There's actually no error right. But exit anyway right. Nothing happens. So that worked. I leave it to you if you want to do other kinds of things for that case. Also SDL log here is doing info. Usually this kind of errors that crash the program is not really info. It's error. So maybe you want to look up SDL log error and you can specify the category as the first argument of the second argument would be the message. That's something extra credit you can do. Okay. I'll leave as is right now. Anyway, let's revert that mistake. It's not equal to zero again. You know that working fine. Let's cook. Let's keep going and remember the next thing I told you we need something to shut down or close. So I'm going to add a new member function to the game class that is a public function. I'm going to call it shut down. And that's going to be to clean up anything that you have into shut down SDL. Now I have the declaration. Let's go to the game.cpp and add the void shut down. That shut down is part of member function of game. So we have to proceed the name of the function with game, call and call. Name of the class, call and call. Now let's go back to main.cpp. And let me go to the end. And let's copy these statements to free the surface, destroy the window and quit. Actually there's a lot of work to do for the free surface of destroy window. So let's leave these for later but they are going under shut down. So I'm just going to take the SDL quit and move it to shut down. And in place of where we had quit I'm going to call game.shutdown. Now because game is in the scope for the main function here, this very long function, that should work fine, right? Remember I instantiated a game object online. In line 174. The shut down should be fine, right here. Let's recompile and run a.out here to see if it works fine. Seems to be okay. Alright so what we did so far is add the class, the game class and then we declare the initialized shut down functions. They both return nothing so it's void. And they're public to make sure we were able to call it, right? Because if it were private, which is implicitly if it were defined in line 2, right, before the public keyword with no keyword at all. We would not be able to call game.whatever because the instance cannot access something that is private to it. Only from within the implementation function, member functions themselves, can they access the private members of the class of that instance of the object. Okay let's see what else we can do. So right after initialize, we have this sdl create window. So it creates the window checks for an error and then it gets the window surface. We can isolate the logic to create the window. So let's copy this. Let's go to game.h. I'm going to add another function. I'm going to call it maybe let's we can call setup window create window. So we're creating the window. So that might be one, the same name, right? Or we can say setup window. Gaming is always a hard thing in programming, right? So if you have a better name, you're free to choose it. I'm just going to create window. How about that? Now it's void because it doesn't return anything. Now let's go to game.cpp to define that. Now I'm going to say void game colon colon create window. No parameters. Paste that code in here. We're making a variable for a pointer to an sdl window. We already have sdl included so that's fine. Create window. We use this constants. This constants come from the sdl header. Now we're using window width window height. They are not in scope for this file so let's go back to main and see where they are defined. I think they are at the top of global variables. If you can recall window width window height they're here. So I'm going to copy those. I don't want to make them global anymore. So we can place them under the game class as private variables. So go to game.h. I could have them here. I can int width int height as private. Right before public when there is implicitly private. Of course you could write private but I'll leave it without. Now that will be in scope so all the member functions have access to the private data numbers of the object instance. If you know sdl plus you probably want to add more keywords here to make this constant on perhaps even static something like that. I'll do that for later if you want to do it up to you. Let's just see if it works as is. Now I assume that will be available now and then we check if the window variable is null. See error is from the IO stream. We have it here. And fail to create, get errors from the sdl header. We have it included here. Now we have the return to here. We want to replace that if the exits or whatever means you devised to handle all those problems. Now one thing that's of interest is the window variable here will lose like right after the function create the window when it's complete window will no longer be in scope so it'll be gone essentially. But we're referencing this window throughout our main.cpp file right so we got right after the creation of the window we're referencing it many times I think perhaps not many times but we are referencing it. So what's going to happen now is we are going to have the window as a private variable of the game class the same way we did with the constants for the width and height of the screen. So here in game.h I have to add that window right so it's a pointer to an sdl window only called window. We have it here line four. That's just the declaration of the variable to say it's going to be there so you don't have to initialize. The initialization or the setting up is in the create window function so we assume that create window will eventually be called otherwise window will just hold something that's not meaningful some memory garbage. Anyway going back here to game.cpp just to make sure we have we no longer need to declare the variable because it's already done line 19 we don't need the dive pointer here because that variable is already declared as a private of the game class so I just need to say window to define it. Window equals sdl create window. Now going back to main.cpp we have to replace everything there with game.create window but the thing is right now as we have it we actually need the window otherwise it's going to fail because of the window surface and all that stuff so we have to be patient until we could get something working so I'm just going to remove this. Alright now we have sdl surface window surface got window surface now this is something that we're going to be referencing in the loop here and so on. So that's something we have to think about. Let's see I'm going to place this the window surface will also be a private member of the game class so I'm going to take this I'm going to copy actually I'm going to place an in create window to under create window add that sdl surface window surface but instead of declaring it here it's going to be defined so no need for the type and the game.h you actually place it here sdl surface pointer window surface. So now this window surface variable will be available to any member function of game so create window will have it access so we can set it here in line 32 and we can reference it throughout any member function of game. Now we can remove that. Now we have the game map tiles be initialized here we can take that too. Okay very mind that we're going to be cop cutting and pasting code so there might be issues that will pop up later on as we try to compile this so please be patient so after removing everything there to the game class we're going to compile and it's likely that it's going to give us a lot of errors and we're just going to go one by one take it one step at a time and fix each error right so let's let's take the game map tile so this variable holds all the game map tiles we can take that and make it a private to the game class entrance position exit position we can this seems like an initialization stuff so we can also take that as private okay so let's cut and game.h I'm going to place it here game map tiles and position and so on now this one seems okay I'm concerned I don't know if C++ will like the initialization in here we'll find out later usually we initialize things in the constructor okay we're going to do one later so just give me a moment so we have these entrance position exit position we're using tile size where is that defined let's go back to main.cpp we have tile size is a constant our global variable here I'm going to copy it I'm going to also make it a private here in line 3 I'm going to make a new line to line 4 and it's there now I want to make these two for now actually want to make that definition in the constructor so usually when you have these private variables in the class header right in the class sorry so the header file has the class declaration right you define that you say that here's a class these are all the methods right the member functions and these are all the attributes and in the cpp files where you implement those so typically you want to keep the header file without any initialization so for these two I don't want to initialize here I want to do that in the constructor so when the object is instantiated that is when you create an object of the type game that's when you set the the attributes to a default values initial values in this case we have an entrance position with the initial value of zero zero tile size tile size same for exit position so this part to the right hand side with the equal sign will be done in the constructor so to make constructors simple you have the game class inside as a public just say the name of the class and parentheses right and for the constructor that I want to make the default there's no parameters so we don't need anything to instantiate so that's the constructor right here now to implement the constructor go to the main let's go to game that's cpp I'm actually going to go up to the top because I like putting it in the top so I'm going to say game the same way I wrote in the header but I'm going to define the with the function block now because this construct is part of the will be part of the game class I have to say game call and call prefix and this is the constructor now in the constructor we're going to do the setting of entrance position and exit position I'm going to copy the code here but before I do going there I'm going to remove the definition so we only have the declaration as the L rack introsposition as the L rack exit position back to game dot cpp I'm going to paste that code but since we already declared the variables we don't need to type to the left hand side of the name of the variable and I just messed up the key here like this all right tile size was it's not explicitly included here but it's included from game dot h sorry it's not from the outside so tile size is private data member of game right of private variable so it's here my actually define these inside in game class is probably want to make that static but I'll leave that as is anyway we have access to that so that shouldn't should not be a problem we have the tile size because it's a private data member we are inside a member function or a constructor in this case so we have access to that okay let's keep on going so since we already have these lines 12 through 14 in the main dot cpp it's already a private members of the game class I'm going to remove it okay going down back to where we were after create window so we had the entrance position exit position when the object is instantiated in line 170 it's going to call the constructor and it's going to set an entrance position exit position to these values so that's fine now the case for the what else did we have the game map tiles here it's initialized here I don't know if this will work I have to find out later if it doesn't work probably have to make a loop in the constructor so we set every single element to zero just to have an default value a default value I'll leave that for later if that is a problem now we call load game map now let's take this load game map function here and let's move it to the game class so we're going to reference the an instance of the game class that calls the member function load game map as so now let's go to the definition of load game map I'm going to just basically cut everything and I'm going to game dot cpp and I'm going to paste it here now I want to make this a member function of game so I'm going to put before the function name game colon code all right and before we go to the header to declare there let's walk through it so to see if there are things we can improve so the parameters we're going to see if we can make them private members of game so first we're calling i f stream i have stream is part of the f stream so I need to include at the top include f stream game map file game map fine is open interest line and so so so we have the changing of the interest position dot x and position dot y but remember just what we just did we now have interest position in x position as a private attribute right it's an attribute of the game class but now we have load game map as part of a member function of the game class so it should have access to the private data member so we no longer need the parameters two and three right the interest position of the exit position so that's no longer needed we can reference it directly it's in scope and after that we have the tiles at index and so on but what is it what was it doing before if you can recall going back to main dot cpp in the call to load game map uh remember you don't need the exit and interest anymore so remove them you can see that I think we have game map tiles here but these were actually moved as private data members of the game class so actually we can reference it directly too so these tiles here that we have we no longer need as parameter and because the variable name is styles here I have to rename it in game dot h I'm just gonna say tiles okay like so so it corresponds to these tiles we're talking about right here okay now going back to main dot cpp we simply call game dot load game map and we don't need anything at all anymore because the tiles will have been stored in the instance of the game class as a private variable mind you these is in scope as long as the object is in scope and the object game will be in scope as long as main is running so that should be totally fine because the scope of the game variable it will be alive up until the end of the main function which should be returned zero which is going to finish the program and shut down so that should be totally fine so the private members of game will be these will be still in memory there's no problem with memory leak so far memory leak is when you allocate memory and then you forget to deallocate and then you have that chunk of memory taken and you keep losing losing more memory space because you simply forgot to deallocate okay now let's look at this debug function called print game map it's pretty much the same thing we had the same arguments right so i'm just going to delete them and i'm going to move that to the game instance the class so let's go to print game map we're just going to cut go to game.cpp and mind you i forgot to declare the function names in the header so let's put here game.colon colon before we do that let's just walk through print game map so we have intiles we already have that it's private interest position already private to the game instance exit position already private don't need that we have c out we're already doing ios stream in the top so we include ios stream so one so one we have the loop and so on so this should be totally okay now we need these two functions in the header so game.h go there and add them void under the public right line 15m say what was it load game map and print game map and they're both void with no return void mean return nothing and no primers save the file save the file save main okay looking everything's looking good we're almost there so take a moment to rest a little bit okay so let's go on now we're saying game map surface to create game map surface we can also take the game map surface and make it a private variable in the game class let's do that i'm going to copy the left hand side go to the game.h under the private variables i'm going to declare game map surface like so and mind you there's one thing i want to before i go on i actually want to add includes here to correspond to everything we're using so let's see int is built in we don't need sd albino we're using that so i want to include sdl to slash sdl.h output angle brackets instead i like to do this way so include because we're using here surface fine fine fine fine there's nothing else so because this is a red in game.h if you go back to game.cpp you don't have to have it in my tree so i remove it now i also want to add an if if not def guard okay that's something very common that we usually do and and that's pretty much enclosing the whole header file in the if and def if not defined i usually give some sort of unique name you i have a pattern that i like to use is underscore and the name of the class all uppercase game followed by the extension of the file h like this or you can add more underscores up to you so it's a unique item in a fire so if this is not defined we're going to pound define the same thing and we're gonna and if here at the bottom so essentially this is a guard so if you have if this file is included multiple times you won't have a problem with the compiler it will only be included once so if this identifier is not defined you define it and then here's the code and end the if okay and the if and the next time you include this file it's going to check is this game h not defined because the it is already defined because it passed once it will not execute any it will not place anything that's enclosed so we only have the contents included once you can read more about it uh it's the guards header guard files okay in any case that's gonna every single header file gonna have that to protect us from those include problems okay let's go back to game dot cpp remember we were doing the game map surface so i added it here go back to main dot cpp now this part i'm gonna do what this is actually something i have to think about because you're creating the game map surface essentially you are setting the game map surface so you load the game map then you create the surface with the tiles right because map game map surface is now a private main private variable uh this setting can be done within the game member the member function of the game class and then immediately after have to paint up the entrance of the exit so maybe these have one could be in one single function what do you want to call it that's always the big challenge so create the game map put paint we can call set up game map surface or or create game map server the same name i don't really know what to call it let's say uh create let's just use the same name i'm gonna say game dot create game map surface and this code is going to that new function cut let's go to game dot cpp let's say void game create game map surface no parameters paste the code uh since we already have this variable declared remove the type uh so we need this function here let's copy actually that's the same no same name so that's gonna be a problem right we have to change this let's call it let's see let's call set up game map surface for now now let's go back to main let's copy this function go back to cgame.cpp before set up game map surface paste that function and add the prefix game colon colon before the name now we need to put these in the header so let's go to game dot h and add for sdl surface star create game map surface and there's an argument there right what was it it's all the it's the tiles but now because they are private we have access to them so let's see in tiles i think we don't need it here but it might generate problems because we gotta think about it let's see if i remove it we have the surface oh we have this too and the surface create surface rectangle tiles is here surface format field rack uh surface return surface maybe it's okay now it's called from here now we don't need that argument anymore right because it's already accessible if it's null have some errors maybe we want to use exit here set a return and paint entrance the paint exit we need those let's grab it from main dot cpp let's cut that go back to game you can paste it up here don't forget to add the game colon colon prefix to both we have these let's check the arguments we're ready we have access to the entrance position so we don't need this it should be there already because it's a private variable of the instance same for paint exit don't need a second parameter and it's being called from here with the game map surface now let's add these functions to the header files so we have paint entrance paint exit i'm just going to copy this i'm going to remove the separate the prefix and these braces and i'm going to duplicate and say paint exits now i didn't add the setup one right void setup game map surface that's there too now let's go back to let's go to main dot cpp what did we have here let's call this setup right remember to change the name setup line 89 okay um let's see we are kind of halfway done i think i know it's a long thing to reflect for these and move to places but it's essentially the same approach you just grab the function put in game dot cpp add the prefix see what variables are no longer are already accessible because of the virtue of the instance of the class the private members you can access from any member function that you have without any just you just can access it there's no need to be passing stuff around and just proceed like that i think we're going to take a break for this lesson and i'll see you in the next one we're going to finish everything and as i said before we have to be patient when we do this and there will likely be errors as we compile after doing all the migration of the code and we're going to tackle one error at the time starting with the error at the top and then we're going to address all the issues that the compiler will raise we'll see you in the next lesson until then
No comments yet (loading...)
No comments yet (loading...)
Did you like the lesson? 😆👍
Consider a donation to support our work: