COLTON OGDEN: All right. So with Pong 4, we talked about random number generation and seeding the random number generator. We allowed our paddles to not move beyond the top and the bottom edges of the screen, and we applied a velocity to our ball in the Update function, which we talked about, to allow the ball to move sort of randomly on its own without any input. And in addition, we also talked about game states. So having a Start state and then a Play state, so that we can have transitions between different functional aspects of our game. Now, this update really doesn't introduce a lot of new visual difference. You can see the screen looks exactly the same, more or less. The real big difference here is we're going to look at refactoring our code base and talking about object-oriented programming. Because currently, the source code is getting a little bit unwieldy. We have a bunch of variables that are tied to different sort of conceptual objects in our scene-- the paddles and the ball-- but everything kind of feels like it's out there in the ether, right? Nothing is really compartmentalized in a way that feels ideal. So with this update, we're really looking at just refactoring the code. And in order to do that, the first thing that we really need to talk about is oriented programming. And the first thing about object-oriented programming, the most foundational piece, is the class. So what is a class? Well, so far you've been dealing with variables. You know, ints, strings, chars, that sort of thing. Very simple pieces of data that you can group together. And likely you've talked about structs. And structs are very similar to objects in most programming languages in that they compartmentalize data in a way that's semantically meaningful. With classes, not only do you get data that you can put together, but also functions that are tied to a group of data, essentially. And you can kind of look at it like this, where you have a class diagram, like a car blueprint, more or less, where you say, a car should have attributes, like how much fuel is in its tank, what its max speed is. And some methods, like, for example, to refuel for an individual car. To set the speed of that car. Maybe set it so that it's driving at 60 miles an hour. Get the speed of the car. What is that specific car's individual speed? And then of course drive, which means to go in the direction once you have set the speed. Now, this is all sort of a conceptual look at what a car should have intrinsically, but it's not a specific car. A car, once it's out of the factory and it's been manufactured using this class, this blueprint, it becomes an object. And classes and objects are two terms that you'll hear very frequently in the context of OOP, object-oriented programming. And in fact, the name is object-oriented programming, not class-oriented programming. But classes and objects are inextricably tied together. The class is the blueprint, the object is what actually comes off the assembly line and functions within your code. And you can have, by virtue of that, many objects that are derived from this blueprint. You can have car 1, car 2, car 3, where they each have their own fuel, their own max speed, their own color, their own make, their own speed at that point in time. And that is the beauty of it. You can spawn six cars, six paddles, but you only need to have the code that actually specifies what the object should have one time in the class. Therefore, we sort of de-duplicate this information. We only have one definition for it as opposed to, let's say, paddle 1's Y, paddle 2's Y, et cetera. Now we just have to specify what should a ball, what should a paddle, have as attributes? So just some bullets that I put together. You can see blueprints for what a class is. They are a bundle of data and code. And all of this sort of describes essentially what I just talked about. And we're going to take the concept of the class and the object, and we're going to tie it into the paddles and the ball specifically for this application. So that's it for the slides. Not a whole lot of code and new functions and that sort of thing that we're going to talk about. But we are going to dive into refactoring, and we're going to specifically use a library. So we're going to use this class library by Matthias Richter. It's a very simple way to allow us to do object-oriented programming in Lua. And it's possible normally without this library, but there's a lot of ceremony and overhead as opposed to using this library that I thought was pedagogically meaningful for us to instead focus on what it means to use classes and objects, use a simpler library. Let's not get involved with the nitty-gritty way that Lua at the lowest level does object-oriented programming. You can always look into that if curious, or even explore the class library, but instead we're going to use this class library. And it's a really clean, simple approach. So the very first thing that I want to do, I'm actually going to create a new file. I'm going to call this Paddle.lua. And notice that I capitalized the P. In object-oriented programming, it's very much the norm, and expected, that you should capitalize the name of classes. In this case, we're defining a class. It's going to be called Paddle, so make sure that it's capitalized. I'm going to create another file, and this one's going to be Ball.lua. So first thing that we're going to do, I'm going to work on the paddle first and then the ball. So I'm going to say paddle is equal to class, with these curly brackets there. And this is just the way the class library expects to work. What this essentially does is it takes the class library from the-- first of all, we're going to need to import that library. But it takes that library and it generates a new paddle object that's going to have methods and fields that we can describe ourselves following this declaration. Now in order to get this to actually work, you don't normally get class working out the gate. You obviously need this-- it's not a keyword that exists in Lua. You need to import the library. We're going to go into main.lua, and right where we have Push, just alphabetically, I'm going to say class equals require class. Just like that. And so now, what we'll eventually end up doing is saying require paddle and require ball. And because of the order in which we're requiring these things, because class is coming first, followed by push, require ball and paddle are going to have access to this class variable. So they are global variables. And for what it's worth, too, any variable that you declare without specifying the local keyword-- local class, for example, equals require class-- is accessible anywhere in your application. But local just means that it is local to that scope. And you've probably talked about scope in CS50 in the context of C and Python. We haven't used local yet. We will eventually get into doing that. But for right now, any variable without local just means it can be accessed anywhere. Which is why class is accessed through the Paddle file, even though it's separate from the main.lua file. So I'm going to go in here. And I'm going to say each paddle-- or actually, rather, any class that you define needs to have a function that creates the object, that actually does the work of assigning the right fields and getting it ready to use. So we need to define a function called init, specifically, Paddle:init. And this colon, again, is very important. The colon is what allows objects to call methods. So so far you've seen dot being accessible and colon being used. And colon just means that if an object exists, it will use a function that works specifically on that object. We've used dot. You don't need to instantiate an object. You can use a dot method anywhere in your application. Indeed, you can see that all of the love methods, for example, are dot methods. You don't have to create a love object to use love.load. It just is a function that exists somewhere in a module called love, specifically a table called love. But if we're dealing with objects, and we want every object to call init on itself and use its own data, you need the colon. It's very important. So for the paddle, I'm going to say that I want a couple of initializing fields. I want an X, I want a Y, I want a width, and I want a height. So this will allow us to specify in our main.lua, when we create a new paddle, we give it those four fields. And then it will take care of actually storing that here instead of having to store those different fields in our main.lua file. So we'll say Paddle:init. And then this is another very important thing to know about object-oriented programming. You need this self keyword. The self means this particular object that we're instantiating inside main.lua, we're going to use itself. We're going to refer to itself. Self dot something means it belongs to that object. So if we create multiple paddles, self, when in the context of those individual paddles, will mean whatever that paddle is, as opposed to some global variable somewhere. So self.x is going to be equal to X, self.y is going to be equal to Y, self.width is going to be equal to width, and self.height is going to be equal to height. So it's going to store those values internally. Now another thing that we're going to need to do is we're going to need to update our paddle. So in our main.lua, so far we've been taking care of all that work ourselves. Instead, we're going to defer just a little bit of that to our actual Paddle class. So what we can do, again, we're going to need access to however much time has passed since the last frame. We can pass that from main.lua down to our Paddle class by just specifying this function. Just take dt and making sure in main.lua we feed dt into Paddle:update. So what I'm going to do is I'm going to say if a self.dy-- and that's another thing we're going to need to also take care of, is self.dy. So normally there is no velocity unless we're pressing a key, right? Dy is a variable, a value that we're going to keep track of, that's going to work inside of Paddle:update. So Paddle:update will take care of changing our actual Y value with regards to dy. So if dy is less than 0, then what we're going to do-- remember, if it's less than 0, then that means it's going towards the top of the screen. And if it's positive, it's going towards the bottom of the screen. The y-axis is inverted from what is commonly the case. And we're going to say also if dy is greater than 0-- because it can be the case that dy is literally 0, which means that we should not do anything, really. We shouldn't move the paddle at all. We're going to essentially copy the code that's in main.lua. So if I go over to main.lua in our update function here, you can see that we have this player1Y is equal to math.max of 0, and then player1Y plus negative paddle speed times delta time, we're essentially going to have this. We're going to have-- well, it's going to be called paddle1, and then it's going to be colon, update. And then paddle2:update. But we want to take the actual functionality here in main.lua and put that into the Paddle class. So I'm just going to copy this line of code. I'm going to bring it over here into Paddle.lua. And I'm just going to paste it in there. And same thing for the other case. So I'm going to copy this, bring this over into Paddle.lua. And now we no longer have this reference to player1Y. It's instead just self, specifically, self.y in this case. So I'm going to do this. I'm going to Command-D, if you're in VS Code, to select multiple occurrences of the same variable. I'm going to say self.y, just like that. And no longer is it negative paddle speed, or rather just paddle speed, it's instead selft.dy. So we'll just do self.dy, like that. Now of course, we have to set the dy, and for that we need to check whether the keyboard buttons are being pressed. And we can do that from within main.lua or in here. We're going to do that in main.lua. But we're going to go ahead and go back here, and I can actually just do this. We'll say paddle1.dy is equal to negative paddle speed. And then here we'll say paddle1.dy is equal to paddle speed. And then we'll say if it's the case that none of those are true, we'll say paddle1.dy is equal to 0. Simple. And we could do this also in the Paddle class, but we'll keep it in here for simplicity. And so same thing here. Instead of using player2Y, we're just going to say paddle2.dy is equal to negative paddle speed. And here we're going to say paddle2.dy is equal to paddle speed. And then else, paddle2.dy is equal to 0. So pretty simple, pretty straightforward. And then the last thing, too, is we want to defer-- normally we've been rendering our paddles inside of main.lua. Inside of, yeah, inside of main.lua here, just explicitly by drawing rectangles. Instead, what I would like to do is say paddle1:render, and paddle2:render, like this. So what I can do is I'm just going to copy this call as I had previously. And we're going to define a new function called render, just like this. I'm going to paste that in there. And now we're no longer using hardcoded 10, player1Y, 5 and 20. We're instead going to say self.x. We're going to say self.y, self.width, and self.height, just like so. And remember, we're storing a reference to these variables through here from the init method. We construct this in main.lua, it gets stored, and then you can reference it in the later functions. So for that, I can actually get rid of this. And now we have those being stored just like that. And just doing a quick look here, make sure everything looks OK, which I think it does. And I think we're just about ready to test to make sure this is running smoothly. So let's go ahead. And global paddle 1 is not set. So let me make sure-- oh, I actually didn't instantiate the paddles. Very important step. So what I'm going to do is, right where we have this player1Y and player2Y, I'm instead going to instantiate the paddles. So let's say paddle1 is equal to a Paddle. So I'm constructing it by just calling the actual word Paddle, like we defined here at the top of our Paddle class. And I'm not calling the init method explicitly, but when you use these parentheses here in what's called the constructor, init gets called through the class library sort of implicitly. And this is syntax that you would see in most programming languages that are object-oriented, Python among them, Java among them. So I want to specify the different attributes for the paddle. So remember, it takes an XY within a height, so we'll say 30, and the Y will be also 30. Actually, I think for the X I had it set to 5. So we'll say 5, and then actually 20. And then we'll say it should be 5, 20. So actually uniform dimensions there. And then for paddle 2, its X is going to be virtual width minus 20-- or minus 10, actually. Virtual height minus 30. And we'll say 5, 20, just like that. So if I do this again, it's white. So again I need to reset the colors here. So 40 divided by 255. 255, 255, and 255. Save it, run it, boom. There we go. So I can indeed move-- oh it looks like I have the keyboard-- it looks like I messed up the keyboard shortcuts there. So let's go back over to the update function here. So if keyboard is up-- oh, I see. Wait. IsDown w, paddle1.dy and then paddle2.dy. So paddle 1 is set to 5, 20, 5, 20. And then virtual width minus 10. Virtual height minus 35, 20. So let's go ahead and see what's going on here. So paddle1.dy is equal to negative paddle speed. Paddle2.dy is equal to paddle speed. Then paddle gets set to-- oh I realized I'm looking at Paddle right now, and I used negative self.dy when I added the velocity to self.y if it's less than 0. But actually, we should always be just adding self.dy in this case. And it will be negative depending on how we've set it earlier. So I'm just going to set that to plus self.dy. and let's run this just to make sure it works. And it works. OK. So I'm moving up and down. Everything looks like it's working great. So, awesome. Paddles work just like they did before, but now they're in a class as opposed to just being variables that we're storing inside of our main.lua. So now let's take a look at the ball. So let's say I've got the thing going on there. Go to Ball. So it's just going to behave in a very similar way to the paddle. So I'm just going to say Ball is equal to class, with curly brackets. I'm going to say that there should be an init function that should take in a XY width and height as well. I'm going to define an update and a render function. And for the init, I'm just going to do much like we did with the other class. And these are similar enough such that you would almost think they could be-- and this is more of an advanced topic. But there's this thing called inheritance, where you can actually have a base class that you can spawn children classes that take all the attributes of the former class and add some customized behavior. And they're similar in this case because they're both rectangles, or quadrilaterals. But we're not going to do that. It's little bit too much legwork. But you can absolutely do that, depending on the use case of your application. And so again, we also are going to need to have a self.dy, but also a self.dx in this case. And actually, we're going to go back to main, and we can just take these four lines here with the comments. I'm just going to delete them, because we don't really need them anymore. Because this is all going to get taken care of inside of the Ball class. So let's go ahead and I'm actually just going to delete them altogether. And we're going to say that this is self.x. And actually, I'm realizing, oh, actually, that's fine. We'll leave that. We'll just say that. And we'll do self.dy, dx, sorry, is equal to math.random of 2 is equal to 1 and 100 or negative 100. Again, ternary expression in that case. Randomly between 100 or negative 100. And the self.dy to initialize the velocity there as well. And I'm going to also create a function called ball reset, because you'll notice in main.lua, whenever we go over to the restarting of the state from play to start, we get the ball going right back in the center of the screen. And it's kind of cumbersome to call those four lines over and over again. So what I'm going to do, is I'm going to copy these lines and just get rid of them. And instead, I'm just going to call ball reset, just like that. And I'm going to copy those over here into ball reset. I'm going to change ball x to self.x and self.y. And then I'm going to call this self.dx and self.dy. Not xy, dy. Just like that. So now the ball can reset pretty easily. And it's going to get set right into the center of the screen. So that's great. And lastly, we also do need to render the ball. And update it, actually. So this is simple enough. Let's go over to main. Let's grab the render function from here. So I'm just going to grab this. Again, replace it with ball render. And we're going to need to instantiate a ball up here as well. So I might as well just do that. Where do I have that set up? That is right here. So we'll just say ball is going to be equal to ball. And the xy is going to be virtual width divided by 2 minus 2. Virtual height divided by 2 minus 2. Five and five, cool. So that instantiates the ball. Let's copy the render function over here. And this, it is going to be set to fill. We're going to say self.x self.y. And not for four actually 4,4. Yes, that works fine. And then that should be it. That should be good for that. Let's run this, make sure it works. And it does work. So the ball is right in the center of the screen. Now, I hit Enter and we change the state, we notice that we don't have this thing called ball.dx anymore in main, because we haven't actually transitioned the movement, the update portion of our code into the ball class. So what I'm going to do, is I'm going to say, I'm going to copy these lines. And I'm going to say ball update of delta time. I'm going to go back over the ball and in update, just going to paste those in. Now it's not ball x and ball y anymore. It's self.x. So I'm going to copy that, self.x. And it's not ball.dx, it's self.dx. And we'll do the same exact thing for ball y. So self.y. And this becomes ball or self.y. rather. Save that, run it, start, and we do indeed have the ball moving through the screen. I can press Enter, and it restarts, goes in random directions. So everything really is working the way that it used to be working, except now we've cleaned up our main function just a little bit. I mean, it's still quite long. But instead of having a bunch of lines of code that do a bunch of different things that are very sort of specific, now we just say paddle 1 render, paddle 2 render. Ball render. It's much more readable. In addition to cleaning up the code in terms of volume, we've cleaned it up in terms of readability. And that's arguably more important even. So that's it for the sort of the class update. So in the next update, pong7, we're actually going to get to some interesting stuff. Oh, actually, sorry, pong6, I had my slides messed up there. Pong6 is a very small update. In this case, we're going to talk about FPS. So we're going to have a little bit of code that tells us at the very top left, what our frame rate is. Because this is important. If you played a lot of games that have a debug mode or the like, you will see that they, it is important to monitor your FPS so you can make sure that your computer can run your game. Now, of course, this game is very simple. So this is a little bit less germane. But this might be useful for your own project. So we'll take a little bit of a breather from the hard stuff and focus on the FPS update with pong6. So see you then.