Subtitles section Play video Print subtitles COLTON OGDEN: Good morning, good afternoon good evening, depending on where you are. We are currently based in Massachusetts in the United States. But I know we have many viewers all over the world. This is CS50 on Twitch. My name is Colton Ogden. And today we're going to be taking a look back at game programming, which is what I usually program in. Last week on Friday we did HTML. Today we're doing games in LOVE and Lua. And if you don't know what those are, we'll go over them. But they've been used in many streams before. Today we're going to look at a pretty famous game. If you grew up in the 90s or used computers in the 90s you probably had or at least used a Windows machine. And on many Windows machines, there was a game called Minesweeper and it looked something like this. Let me transition to my computer. And Minesweeper has had many incarnations over the years and certainly not just limited to Windows machines. But this is what Windows '95 Minesweeper looked like. So you have this grid of tiles. And we've talked about 2D tile grids before on stream, particularly with snake as a good example, that snake was based around the sort of 2D grid idea. But you have a grid of these blank tiles that all start off unrevealed. And you click tiles individually. And when you click on a tile, it basically tells you how many bombs are adjacent to that tile. And it considers also diagonal offsets relative to that square. So not just up, down, left, or right but also whether there's a tile in the diagonal direction. And so through inferring based on what you see, what numbers you see when you reveal tiles, you can get a sense of where the bombs are. And if you can happen to reveal every tile on the map except for the bombs you win the game. And you get a certain amount of points, as you can see up at the top of the screen. I believe on the left here, this may be the points and on the right would be the timer. I'm not 100% sure. But in any case, that is the game that we will be talking about today. So let me just read off some of the chat here. And everybody let me know if you can hear me, if everything sounds good. We had a network change, although that shouldn't affect this stream. But I did have to rewire everything up to make sure that it worked. So shout outs to everybody who joined the stream early. So, sashka32, bellocures, lewie0416sdo, that's a name that I haven't memorized yet. I'm not sure if you are a first timer or if you've actually been here before. Mkloppenburg, thanks for joining. You and I chatted briefly in the chat beforehand, saying that you really like the code review idea. So we are going to be doing a code review episode or potentially more episodes in the future. Moididie, thank you very much for the follow. So what we're going to be doing is, we'll be soliciting and we did this on Facebook, where we essentially are soliciting folks who are interested in us reviewing their code. And so I'm trying to think offhand. I don't have a Bitly URL. It'd be cool to have a Bitly URL or something. Let me see if I can really quickly do that. So if we have a-- just go to Bitly. So a Bitly is a URL that allows you to shorten the longer URLs. So we have a form. Essentially here's the gist of the idea for the new program. So you have a GitHub repo or a gist, those two. We're formally saying gist or GitHub repo is public URLs for your source code. Sashaka32, you didn't get a haircut. You're right. I promised I was going to get a haircut on this last Sunday. Couple of things happened. So firstly, we had a blizzard here. And it was kind of a pain to go out in the blizzard in super cold weather, super freezing. And especially because I would be getting a buzz in super freezing weather, I was like, no. I'm not going to do that. And then I tried to get it yesterday and the person that cuts my hair wasn't working yesterday. So I kind of shafted myself a little bit. But I'm going to say, I will do my best to get it done by Friday if not over next weekend. But yeah, I screwed that one up. Anyway, the code review idea. So the code review thing is something where you, if you're a Twitch viewer or otherwise if you're on YouTube or whatnot, you can submit to us your source code for some repo or a gist. And we'll take a look at it and on stream, David and I will grade your code sort of in the same way that we do with CS50 students at Harvard, where we just take a look at your style and your design. So basically, how well is your code formatted and then how well have you engineered your application? Do you have duplication of code? The dry principle, those sorts of basic things. Like is your code well engineered, well designed? Does it look nice? To sort of give you some feedback if you're a beginner or an intermediate programmer and you want to make sure that you're on the right track, we can do that for you. And it would be a cool bit of new content for the Twitch channel. So what I was going to do was, I'm going to the CS50 Facebook group. I'm going to try and get that URL so I can post it in the chat here for everybody who's actively watching right now. Let me just-- I don't want to take too much time on this. But this is a cool idea. I think it could make some great content. Let me get the URL. Going as fast as I can here. OK. Bitly, here we go. OK, create. Long URL. OK. This is going to be-- copy. Actually, let's make it-- how do I edit this? Customize. So This is going to be CS50 Twitch code. What's the best way to do this? CS50 Twitch code review. It's not really that long of a URL. But I'm going to put in the chat now. So bitly/CS50twitchcodereview. Let me just make sure that that works. Bitly, CS50 Twitch code review. Yes. OK. Cool. So that's in the chat now. So if you go to that URL you can submit your code, your source code. So the form is going to expect a GitHub repo or a gist, a GitHub gist, which is another GitHub feature that lets you basically put your source code into one file that you can share publicly. Ideally the code basis should be somewhat small and manageable so that we don't have to spend too much time grecking through a massive code base. We're not going to bug test, so this isn't like, fix my bug. Although maybe that's something that we can look at in the future for a different bit of content. But this is going to be more like, grade my style. Like, how good does my code look? How well engineered is it? Definitely use it for that intended use case. So I'm going to transition back to my screen here. That's all done. Let me make sure that I'm caught up with the chat. So that was mkloppenburg, Martin posted in the chat about that. So if you're curious what he's referring to, that's what the URL is. Submit that link if you want a chance to have your code reviewed on stream. And we may not be able to review everybody's source code, but we'll take a look. We'll definitely try our best. So thanks so much. [INAUDIBLE], thank you very much. Doing a great job. Appreciate it. Bellacura says hi. Tuxman, let's Minesweep today. And tuxman I think also posted about the source code thing. So tuxman's already in. So follow tuxman's example, join the CS50 review initiative. LightOfHell1 says hello. What technologies will you be using for this, says zakillapotato. And how in-depth will you explain what you are doing and why? Fairly in-depth. I would say probably not as in-depth as prior streams that covered the basics. But we're going to be using two technologies today. So the first technology is called Love 2D, or Love. And it's a framework, a game framework that you can go to love2d.org to download. And the language that it uses for you to actually write your programs is called Lua. And it's a scripting language similar to JavaScript and Python in some ways and kind of its own thing. But download that if you haven't downloaded it already and you're curious and you want to follow along. We've used it in a bunch of streams so far. It's a great 2D game development framework. And we will be using that and covering-- I'll be explaining some of the basics as we go along, but not tremendously. Especially because today's game is a little meatier than some of the games that we've done recently. So great question. Never used Lua. Is it worth watching if I'm primarily focused on web development? Again by zakillapotato. If you're interested primarily in web development, you may not get a ton of value out of this particular video. In the future we'll be doing more web development content such as CSS and JavaScript. So it's CSS next week, JavaScript probably the week after that or at least the weeks after that. Bootstrap coming after that. Love and Love 2D are in the context of game development and pretty separated from web development. So it kind of depends on whether or not you have a casual interest in games. But yeah, that is a great question, good consideration. Ultimately all of learning how to program kind of comes together and is useful. But I wouldn't blame you for ducking out of today and maybe watching a future stream on maybe CSS or Bootstrap or JavaScript. But to sashakan's point, always helpful to explore different areas, exactly. Lua has multiple web frameworks like Sailor and Lapis, says tuxman. Oh yeah, interesting. So I guess if you're doing a transpiled approach, so taking a language like Lua and transpiling it to JavaScript, which is common to transpile languages back and forth on the web. For example, CoffeeScript is a transpiled language. It's very similar to Python. And people program in CoffeeScript and then transpile that to JavaScript. So you could do something like that with Lua, it sounds like. I actually haven't dug into Lua web development too much at all. So I'm not familiar with either Sailor or Lapis. But I believe tuxman's much more familiar with that area than I am. Babbox says, what is the deadline to submit code for the code review stream? I'm working on tic-tac-toe using Python, the one discussed on CS50 Beyond. We don't have a specific deadline. So what we're going to do is have a stream next week, next Friday on the code review. And then if people like it and it's a good stream we'll probably do multiple of them. So I would say deadline, next Wednesday try to get it if you want us to look at it and get it onto the first code review stream. I would say try to get it to us by next Wednesday. But like I said, we may end up doing multiple videos, multiple code review videos. Zakillapotato, your hairstyle rocks. Thank you very much. I appreciate it. It's getting a bit over overwhelming now. Adamantium, what's up? Good to see you. Meowcat, hello. Zodiac, what's up? Zodiac, good to see you as well. Pudavodatel, hi from the Ukraine. I'm here for the first time. Yeah, I don't recognize the username. So thank you very much for joining us. I hope I didn't butcher your username too much. [INAUDIBLE] got the XD. LightOfHell1, will David appear today? Physically no. I don't believe he's in Cambridge so he probably won't be making a physical appearance. But I would pay attention to the chat. He will probably make a chat-based appearance. TheTronBot, hello. Good to have you with us. Bilty URL is there indeed. Coldmind, finished week one of the game dev course. Loving the course so far. Awesome, thanks so much. I really appreciate that. Week one, week one, week one. So week zero was pong, week one was-- what was week one? I'm blanking. Brain fart at the moment. I apologize. But yeah, I remember it starts to ramp up a considerable amount. My favorite lecture, again, the Mario one which I think is week five or week four. Let me know how-- oh, Flappy Bird. That's right. Of course, of course, of course. Yeah, that one's good. So it's nice because it introduces you to sprites and other cool things that get some graphics involved as opposed to just line graphics and shapes and fonts like we've done for some of our streams. Anyone have any idea why running Love 2D on a VM would actually have a higher FPS than my host machine? Graphics drivers, I'm going to assume. If it's not the software, yeah. It's weird because it depends. Like a Mac drawing API like Metal and stuff, or Vulcan, which is a Windows one, if it's done enough in software and then if you-- it's kind of complicated. It's a little bit weird to predict because even that, I would imagine that VMR would try to translate that to the most efficient graphics API for your host operating system. But I don't know. It's hard to say. My instinct would be that it's compiling-- that it's using a drawing API that's more efficient in the simulated VM than is used natively on your host operating system. But I have no data to say whether that's true or not. Packopandas, how do you style your hair? Asking for a friend. It's mostly volumizing hair gel, a little bit of pomade, and then hairspray. And then drying the hair with all of those in it at the same time as well. Yeah. Zero indexing the weeks in a Lua course? Yes. I should have one indexed them. That's a good point, actually. Although we do use Unity, to be fair. So there is a little bit of zero indexing involved. Getting 60 frames per second on my Windows machine and it's saying about 200 or 300 FBS in my Ubuntu VM. Yeah. I don't know. That's a good question. And especially using Ubuntu, I have almost zero experience. I mean, not zero strictly speaking. But I have very limited experience with Ubuntu and Emulation. So I'm not sure. That's a good Stack Exchange question, though. And yeah, to mkloppenburg's point, you could be having your Windows machine frame locking and then your Ubuntu is unlocked. And maybe your Windows machine is actually getting 600 frames per second but your Ubuntu machine is getting 200 and looking as if it's getting more. [INAUDIBLE] says, like the hair, too. Sludgebeard, which is a great name, where are you taking the course? EdX, Vsync, or refresh rate? Yeah, those are related. OK. So awesome. So we caught up with the chat. And so Minesweeper, again, just to restate what we said earlier, we're going to be making a version of Minesweeper. We may or may not have the ability to make the entire game in one stream. We'll try. And if we don't get it finished by today's stream, we'll get it finished next Monday or Tuesday. But we'll take about three hours today, three, maybe 3 and 1/2 depending on how far we get, depending on how long we anticipate having left by the time the stream is over. I have never coded Minesweeper before, so this is sort of a blind run for me. I have thought of the algorithms and done a little bit of research into how people have done it, but I have not actually implemented this myself. So this is from scratch for all of us together, which is going to be exciting. So it's a grid-based game. So we already have that fundamental foundation built upon from prior streams. That will be easy. We'll be doing things fairly well-engineered today just because it's kind of a larger code base. I say, let's just actually start diving in and get some code up and running. And I'll be paying attention to the stream here. I can actually type back into the chat today, unlike last week, where I didn't have that functionality. So I'm going to-- and let me know if this is a good size, if everybody can see everything appropriately. Everything is running at 720. Should look great. I'm going to create in my Streams folder, which is just an arbitrary folder on my computer, I'm going to create a sub folder called Minesweeper. And this will be my folder in which I hold my source code for this Lua project. Love 2D expects that you have everything in a folder or you have a main.lua somewhere in a folder. That's your starting point. I'm going to use VS code, so that's my text editor of choice. It's a highly recommended text editor. It's a really cool text editor. It has a lot of features, a lot of great plugins, lot of great out of the box functionality. But you could use something like Adam or Sublime Text as well if those appeal to you. And I'm sure there are other text editors that are competitors that I don't even know about. I'm going to create a new file called main.lua. For the regulars in the stream or the folks that have watched prior videos, this is all kind of old hat. But this is more just to help bootstrap the folks that are brand new to the stream. I'm going to create a comment block. This is how you create a comment block in Lua. I'm going to say Minesweeper, author, and email. And so there's a few functions. And we've looked at all of these before. This is just part of programming a game in Lua in Love 2D. I'm going to say, love.load. i'm going to say love.update and love.draw. And for good measure, I'm also going to say love.keypressed just so we have that basic foundation. These are all functions that Love 2D expects to exist in your main.lua. It calls love.load once the beginning of the game. It calls update every frame. Get past this DT parameter, which controls basically your frame rate. It controls your interpolation of movement and transformations across different frame rates, rather, so that you can do consistent movement between two machines that are running at different speeds. And then draw, it will basically do everything that's in the draw function after update every frame. So update and draw both happen every single frame. Part of the game loop. This thing called a game loop that Love 2D manages for you, something that often you'll have to manage yourself. NACLEric, caught the beginning of a 650 stream. Nice, nice. And Putevoditell, thank you very much for the host. I appreciate that very much. So yeah. The game loop is sort of this infinite loop that manages drawing and updating and taking an input. And it does this usually once every sixtieth of a second. So that's the frame rate. Bevignite says, there's no mouse click only key press. We will be doing mouse as well. We'll basically be looking to see where their mouse is. So we have to check to see where it is on the grid, which we can do with love.mouse.getcursor, I believe. It might be getposition. And then we will also be doing mouse click as well. This is to bootstrap us into getting a basic application up and running. So I can do something like if key is equal to escape, then love.event.quit. We can bootstrap our application up into something a little bit more sophisticated with defining our own function for checking for input, which we looked at in a prior stream. Essentially Love 2D doesn't let you check for single key presses outside of main.lua, which is a problem if we want to check for single key presses in a separate game state, for example. Which we will probably end up taking a look at by the end of the stream. Now I can run this application, which is cool. And then it gives me a window, a black window. And if I press escape, it'll actually let me quit the application because they defined this bit of code here that says in my love.keypress function, which is again, a function that Love 2D provides you, and it gets this key value for every single key you press, it gets that key value. So we're basically just checking, if key is equal to escape, the string escape, which is how Love 2D organizers its symbolization of the different keystrokes on your keyboard. If the key is equal to escape, then we'll just call love.event.quit. That's Love 2D's way of quitting the application in code. Let's actually figure out how we want to lay this out. So in Minesweeper, and ours may not look exactly like the Minesweeper that's here but it'll look similar, we have all these squares. And depending on where the squares are-- or sorry, depending on how the square is interacted with, so whether it's unpressed, whether it's pressed, and whether it's blank, a numerical square, or a bomb, it has a different look. So you can see that the squares that are unpressed look more three dimensional and that's just because they have a little bit of lighting applied to them. And we can probably do that ourself using something fairly simple. What I want to do is, I want to use Push today, which is a virtual resolution library. And so if I go to something that we did before, like for example, not Snake. I guess we didn't do that with Snake. Did we do the Space Invaders? We did. So I'm going to copy the Push library. So if you're curious, the Push library if you're doing this completely from scratch-- I can't type for some reason. Ulydev Push. So Ulydev is the name of the user on GitHub that created this library. So it's a library for Love 2D that basically lets you simulate using a virtual resolution no matter what your window size is. So you can see here, this is kind of what that effect looks like. You have this large-- this on my actual computer might be, I don't know, 800 pixels tall and 400 pixels wide. But it's looking as if it's maybe only 30 pixels wide and 30 pixels tall. Because using a virtual resolution, it's basically drawing to some tiny image the Mario information, the little simple sprite graphics. And then it's stretching that image to fit a native resolution raster, so something on my actual screen. So it's basically the equivalent of taking something that's 30 pixels and turning it into 800 pixels but it maintains the look of it, the crisp look of it. It doesn't blur it or anything like that, which you can often have happen if you're stretching a texture with filtering applied to it, but you could even simulate in Love 2D if you wanted to. But we're going to do this using the Push library. We're going to get the same effect so we can draw a small 2D-looking grid of stuff, of sprites. And have it fill our window no matter how big our actual window is. And have it look nice and crisp and retro. And I already have-- you can download this library. It's just a push.lua file from the GitHub repo if you type Ulydev Push. I already have it on my machine from a prior stream. So I'm just going to copy this push.lua and I'm going to put it into my Minesweeper folder here inside of a lib folder. And this is kind of conventional if you're doing any sort of development and you have a library, someone else's code that you use. You'll typically put it into a lib folder like this. So that's all I have in my Minesweeper repo here. I'm going to just test that this is working. So we're going to be a little bit more well-engineered today than we are in some other streams just because this is more of a larger code base that we're going to be working with, or at least a more rich set of code and logic. So I'm going to create a source directory. And in that source directory I'm going to create a new file called dependencies.lua. What this does is it basically allows us to store all of our libraries, references to libraries, references to assets, to sounds. If we were to do sprite sheet splitting up, this would be a good place to do it. Just basically any resources that you're using in your game, if those are libraries or artwork or sound, anything like that. So I can say push equals require 'lib/push'. This is relative to main.lua. And what this is doing, is it's importing the push library and assigning it to this push variable. And this push variable is global to our entire game. So regardless of whether it's in one file or another file, we can use push anywhere, assuming that this file itself has been imported in our game, which we're going to do by just saying require dependencies. So now this module, our main.lua has access to push, that push symbol that we created earlier. So in here, now that I have reference to push, I'm going to say push setup screen. Now what it expects is, it expects some references to some resolution variables. So a virtual width and a virtual height and then our actual window within window height. It's going to map the two together. So I'm going to do a another file in my source directory. I'm going to create constants.lua. Another convention which is nice to adhere to, especially if your game ends up being very large and have a lot of constants. Something like Minecraft, for example, which might have a ton of different constants for the different block types in the game. Or some other game, you can extrapolate that to pretty much anything. I'm going to define a few constants in here that are important to my game functioning. So I'm going to say arbitrarily that I want my virtual width to be 384 virtual height 216. I don't know remember if this is the exact resolution I'm going for, but it's something very similar to this. It's like a 1920 by 1080 ratio that looks very similar to the SuperNintendo era. That sort of nice resolution. So virtual width, virtual height. But window width, I want this actual window on my machine to be 1280 by 720. I want this to actually be a 720p window that looks like it's from the SuperNintendo era. So this is how you get that effect. In my main.lua I can now, inside of push setup screen-- sorry, one more thing we need to do, we need to require source/constants. And remember, we're importing this inside of main, this dependency. So we get access to all of these things within main. So constants, in this case. So now I can say virtual width, virtual height, window width, window height. And if we do that, we have to do a couple of other things. So I have to say, push begin. And then push finish. Actually, offhand I don't recall whether it's push start or push finish. We can easily change that. Just to make sure that we are actually rendering everything at the correct virtual resolution, I'm just going to print something to the world. I want to say, hello, Twitch in a love.graphics.print statement. Run the game, and dependencies is not found because what I did is I said require just dependencies. This needs to be require source/dependencies. Again, this is relative to where main.lua is. Dependencies itself is within this source directory. So I'm going to run this again. And the method is not begin. It is start. So push start. I always get those two confused. And then now that we have everything running, I'm drawing a 12 pixel font to the screen. So by default, Love renders a 12 pixel font. And it looks like it's actually pretty large on the screen. So that's the result of the virtual resolution at work. And it says, hello, Twitch. It looks a little bit blurry. So I'm going to make a small adjustment in my dependencies.lua. At the very top I'm going to say, love.graphics.setdefaultfilter. And what this does is, it just says, for every resource that you import or you generate whether that's a font, whether that's a text, whether that's a sprite, anything that's art or font related, make sure that you do no filtering on it. You just use what's called nearest neighbor filtering. So it just basically ends up scaling it up or scaling it down by sampling specific pixels and not interpolating pixels, which is how you get bilinear, trilinear filtering, that sort of thing and therefore that blurriness. This will just make it look completely crisp and completely pixilated no matter how big or how small we make it. So if I re-run this, you'll notice that-- and it may be hard to see on camera, but we do indeed have a much more crisp font. There's no interpolation of pixel data. Now, this font isn't ideal because it's an alias font, right? Whoever designed this font assumed that it would be used for smaller use cases and it wouldn't be blown up, it wouldn't be enlarged, and it wouldn't be set to nearest neighbor filtering, right? So what we need to do is, we need to choose a more appropriate pixel-based font that doesn't get alias. Aliasing is basically like somebody blurring the edges with making the edges a lighter color or transparent. That's essentially what aliasing is, getting rid of the jagged edges. And that's why our text looks a little bit blurry, even though it's not. It's because somebody who designed the font made the edges blurred. Or if the font is generated, if that particular font, that glyph size is generated, it's being aliased in the process. On dafont.com, which is an excellent resource for getting fonts - I'm going to zoom in just a little bit here - we can go to-- looks like they got rid of the menu. If you go too zoomed in, the pixel bitmap section here is excellent for that sort of thing, for the retro pixilated font look. I'm just going to choose an arbitrary one, just to get us up and running. Let me see here. The actual Minesweeper uses this LCD sort of font. It might be hard to see, actually. If I zoom in just a little bit, you can see it just a little bit better there, this font here. So I'm not sure if I'll spend time actually looking for a font like that. But that would be cool if you were designing your own version of this, that would be a design consideration to look at. And the developers of the game made that design consideration because to them, it looked like a bomb defusal type thing. So this is an interesting way to look at or to at least contemplate user experience and making your users feel like they're in the game. And actually, this is a cute image. I didn't see this earlier. There's a soldier using an actual metal detector on top of a texturization of the Minesweeper game. So just basically taking it, putting into the real world. Anyway, somebody's doing it here, too. I'm not sure if that's real or not. But yeah, that's the font that you could consider using. We're not going to do that. We're just going to use a simple bitmap font. So let's pick something. This one's kind of nice and simple. This one's public domain. So we'll use this one. It's called Poco. So it downloads a zip and then it'll download a ttf file usually. I'm going to copy that ttf file into the folder in which I have the stream source code. I'm going to create a fonts directory. Just paste that right into there, poco.ttf. I'm going to go back here. I'm going to go into my dependencies, which I'm already in here actually. Now what I like to do when I'm writing a game like this, I consider fonts and graphics and sounds all to be assets. And I put them into global tables like this. So g fonts with a lowercase g is short for global. This is a good convention and it's often used in game programming. You'll see it a lot in lower level languages, for example, C++ or Java than Lua just because in that particular instance, you need the readability. And there are a lot more uses and easy uses of global variables. But in this case, I'm using a global table called g fonts. And I'm going to set an index to, it was called Poco, I believe. And I'm going to say love.graphics.newfont. I think that's what it was. And then fonts/poco.ttf. And we have a guest today with us here. So he's getting his stool set up there. So this is Rodrigo. What's up, Rodrigo? How are you. RODRIGO SANCHEZ: Hi, everybody. Let me just grab my coffee. COLTON OGDEN: So Rodrigo has been on stream before. So he played at least Mario. Did you play Zelda with us as well? RODRIGO SANCHEZ: Yeah, I did. COLTON OGDEN: I didn't remember 100%. And what are you going to be doing tomorrow? You want to share with the stream? RODRIGO SANCHEZ: Sure, yeah. Tomorrow we're going to be doing a game of 15, which is kind of weird to explain. But essentially you have, say, 16 titles. COLTON OGDEN: We can probably pull up an image, actually. RODRIGO SANCHEZ: Yeah, it might be easier to show a picture. COLTON OGDEN: So, game of 15. And it has the name also, I think 15 puzzle is what a lot of people also call it. But it looks something similar to this. RODRIGO SANCHEZ: So this is what it looks like when it's solved. The numbers are usually scrambled in some way that's solvable. And then you're just trying to shift tiles down to the right, up, or to the left in order to make them in this configuration where everything's from 1 to 15. COLTON OGDEN: And you'll be doing this in Python? RODRIGO SANCHEZ: Yeah. We'll be doing it in Python. COLTON OGDEN: So it'll look something probably similar to this, then. RODRIGO SANCHEZ: Yeah. It'll just be text on the terminal. Nothing too fancy. COLTON OGDEN: This kind of looks like a ridiculous-- Oh, is this actually a CS50? This is a CS50 image here. So this is from-- which year would this have been from? It's in sig one with netbeans, hacker315, it was probably several years ago. But this is game of 80, which is insane. RODRIGO SANCHEZ: Game of 80, oh my gosh. COLTON OGDEN: That's crazy. And this is a solver, too, I think. Maybe not. I'm not 100% sure. Let me get you mic'ed up though, just so that you're audible on camera there. RODRIGO SANCHEZ: I don't know how long I'll be in, but-- COLTON OGDEN: OK. RODRIGO SANCHEZ: --be at least for more than a few minutes. COLTON OGDEN: OK. Well, if you're only going to be on for a few minutes, we maybe don't necessarily need to mic you up. RODRIGO SANCHEZ: I'll be in until Greg needs me or something. COLTON OGDEN: OK. So let me go ahead and I'll just mic you up just so it sounds OK. This receiver's almost out of battery. OK. Where are the other batteries at? For the sake of time, I'm not sure. We don't have batteries around here. RODRIGO SANCHEZ: That's all right. COLTON OGDEN: Well, you should be able to hear Rodrigo pretty well on camera. RODRIGO SANCHEZ: Let me see. People are saying they like your hat. Oh yeah, and Bhavik Knight says, game 15 is not solvable from any random position. That's exactly right. And so we'll talk about tomorrow how to randomly generate the board so that it is solvable. COLTON OGDEN: Nice. Well, I'm excited for that. That'll be fun. In the meantime, let's continue here with Minesweeper. So I explained, I think I talked to you about it in advance because we chatted earlier outside of stream. RODRIGO SANCHEZ: I can be your rubber duck. COLTON OGDEN: I appreciate that. Sometimes I need one of those. But this is Minesweeper. You're probably familiar with it. You've probably seen it. So we're going to implement a version similar to this. I don't know if it's going to be this big of a grid. But we can probably start off with maybe a 10 by 10 or a 15 by 15. And we could expand. Because our code will probably be fairly flexible. We can make it even maybe a random size, which would be pretty cool. RODRIGO SANCHEZ: That'd be fun. COLTON OGDEN: Rigo seems chill, says sludgebeard, Rigo is indeed chill. Spidey27, limit test sounds fine. [INAUDIBLE] 2017 was the last time it was live, says tuxman29. Cool, cool. Sick toque. Not sure what that means. Do you know what that means? RODRIGO SANCHEZ: Not sure. COLTON OGDEN: Sick toque. Maybe that's a type of hat. Is that what your hat? RODRIGO SANCHEZ: It's supposed to be a pseudo winter hat where it's actually R2D2. I'm a huge Star Wars nerd. COLTON OGDEN: Oh, nice. I didn't even notice before. RODRIGO SANCHEZ: If you look at me from a distance it's like, oh, nice winter hat. And then you get up close and it's like, whoa. You're a droid. COLTON OGDEN: So I guess that is the name of a hat, a toque. RODRIGO SANCHEZ: Yeah. It was a gift, so I'm not super familiar. COLTON OGDEN: So the last thing we were doing before you came in was, we were getting a font up and running on the game. So we have just a window right now. Just in, we got the nearest neighbor filtering set up. We have text rendering, we have virtual resolution with the push library. All cool, fun stuff. Now we're going to actually change because if you notice, the font is aliased. It's the default Arial font, which isn't designed for this use case. So it has sort of semi-transparent pixels used on it, or grey pixels, rather. And so what we're going to do is, I went on to dafont.com, got a font that's meant for retro sort of bitmap, pixelated look. It's not aliased, essentially. And I created a new font object here in my dependencies.lua. So within main.lua I can now, because I made g fonts a global object, go into main.lua and say, love.graphics. What I'm actually going to do, so I'm going to do it after push.setupscreen. Love.graphics.setfontgfontspoco. And I capitalized that, right? I did. So now what I should be able to do if I run this is, this will look different. It will be a different font when I run the game. And it is indeed. And now the font itself, if you specify a size that's non-standard for that font, so if we go back to dafont.com, we can see that that font is designed to be used at 10 pixel size. So I believe the default size for the font is 12 or 16 pixels. But what we need to do is, we need to make it a multiple of 10. So it can be either a single multiple, in which case it's 10 pixels, or it can be 20 pixels, 30 pixels, 40 pixels. If you don't specify the proper multiple, it will alias because it's going to try and stretch it out or stretch it down to a size that it's not intended to be drawn at and cause some aliasing as a result of that. So we're going to go over to dependencies.lua. I can pass in a second parameter to the function love.graphics.newfont. And then if I save that and re-run it, it is indeed, if you look at it, not aliased anymore. It's a bit small so we may end up deciding that we want to use a larger version of it, let's say 20 pixels, and then re-run it. And so it looks something like that. That's arguably a little too large. So instead, I'm going to render it like that. Now if we're using this just for a score, which essentially is probably going to be the only use for it, I can simulate this by saying, maybe 000. And if I re-run this, that looks a little bit tiny. So for the score, I might actually decide that I want this to be at 20 pixels, re-run it, and that looks OK. Now what I didn't realize is going into this was that the font is actually vertically margined. So there's some extra padding at the top of that font glyph. So it's rendering at 00, the top left. But it looks like it's getting pushed down a little bit. So I kind of don't like that. I might want to choose a different font for that purpose, for that reason, to circumvent that. This pixeled font has a pretty cool little number glyph set. So I might actually do that. So I'm going to download pixeled. We'll do the same thing that I did before. Open that up, copy that over to my stream. So dev, streams, Minesweeper, fonts, pixeled. And then I can go into here, create a new font object called pixeled. Love.graphics.newfont, fontspixeled.ttf at size and what's the font size? It was also 10 pixels. So in this case I can say 10. Might need to make this one 20 as well because it'll be the same size as the poco font. Render that. We need to actually set that to be our default font. So I'm going to say pixeled here instead of poco. And again, that has margin. So you know what it is? No, there's no margin. I screwed up the virtual resolution. So what I need to do, I'm going to say list of 16:9 resolutions. So earlier I decided on 384 by 216, I believe it was. Right up here. But apparently that's not a 1920 by 1080 perfect resolution. So going to zoom that back out just a little bit. So if you render at a non-16 by 9 resolution, you're going to get some either vertical or horizontal padding, the black padding depending on. And we can't tell that it's black padding because we are rendering our game with a black background. We could do, for example, love.graphics.clear at 0.4. Oh, OK. In that case it also did it. Am I just going crazy? I may be going crazy. No, I'm not. So 384 by 216 is a 1920. Did I just get unlucky and choose two fonts that were both vertically padded by quite a bit? That might be what it is. That's strange. For you, for personal use, pixel mix. Can't use that because I don't think I can technically use that on stream. I might be able to. I like this press Start to play one. I use this quite a bit. So I'm going to do this one again. It looks like I already downloaded it. Let me just make sure that I'm not going crazy. And then JHarvard, dev, streams, Minesweeper, fonts. I'm going to call this one start.ttf. Go back in here. Print 000. Let's create yet another font. We'll call it start. And notice that I have this table that I'm storing just an arbitrary number of these fonts, which is kind of cool. So you can have as many different fonts as you want in your game. As long as you just choose the default font is when you draw, that's the only thing that matters. You will use this a lot if you design UIs that have different fonts. You'll need a table that stores. And this is why we use a table, because you want to make a reference to potentially multiple different fonts. I like Rodrigo's thinking face, says twintowerpower. I like that. OK. So let's go ahead and set the default font to start. See if this does the same thing. OK. So we just got unlucky and I chose two fonts that happened to have some vertical margin, I guess, at the top of it. And it led me to hypothesize that I had chosen an incorrect virtual resolution because off the top of my head, I thought of 384 by 216. But I was correct. That's actually a 16:9 resolution. That's important because, again, it needs to map to the aspect ratio of your actual resolution, 1280 by 720. So essentially what that means is, 384 divided by 216 should be the same thing as 1280 by 720 in order to avoid the horizontal and vertical margins. If I were to run, for example-- and I might actually have to have terminal up. But if I were to do Python, we can also do Lua, but if I say 1280 divided by 720, and this is Python 2 so this isn't going to work. So Python 3. So if I say 1280 divided by 720, that gives me 1.77777 infinite repeating. This is the same thing as 16:9 aspect ratio. That's that number, that constant number. And then if I do 384 by 216, you'll see that that is the exact same number. So those numbers being the exact same means that the window's, assuming that you have them at the same resolution, it'll fit perfectly. The virtual resolution raster, the 384 by 216 raster that you're drawing to, when stretched out, will completely fill and preserve the balance of pixels, that 1280 by 720p window. However, if I were to go into my virtual resolution, for example, and change this to something like 340 by 216 and I run this, you'll notice that on the left and the right I do have some vertical bars to offset that difference in ratio. And that is why it's important if you don't want those vertical bars in order to keep your rasters at the same constant, 1.777. That should be the result, the quotient between your width and your height of your dimensions. So yeah. Fun stuff. OK. Let me just make sure that I can read the chat and keep up with it. Out of bands, array exceptions, says tuxman. Yeah, that only takes a few times before that starts to get a little bit stressful, a little bit overbearing. Randy says, these are not droids you're looking for. These are not the droids you're looking for. Star Wars reference. 1223 in Nepal says, [INAUDIBLE],, I love all CS50 live streams. Awesome. Thank you so much for tuning in. Bhavik Knight says score in time. 11Lewie, you can name the table indices. Yeah. Yeah, you can do that in a lot of languages. It's the same thing in Python, for example. If using a dictionary, that'd be the same thing as using a string as your key and then whatever value you want. RODRIGO SANCHEZ: You can even use numbers or whatever you want. COLTON OGDEN: Yep. Exactly. Yeah, you can use-- and there's some languages that let you, if you have hashable objects, you can have objects act as your keys, which is very cool and kind of crazy. I don't think Lua is one of those. I don't remember offhand if it is. It's kind of an esoteric use case, too. [INAUDIBLE] says, funny bits of programming. So we've come a long way. We have we have some text in the background. I'm going to darken this color, just a little tiny bit in the background. But we're not going to use just pure black. We're going to keep it kind of a different color, kind of this dark gray. This'll be our background. What I want to do is, I want to start sketching out the game, the grid, the 2D grid that will act as our Minesweeper grid, I guess. The things that we can actually click and then reveal them on the screen. So let's do a faux score and timer at the top, which is what we've done in the past. So I'm going to print. So at the top left I'm just going to say, we'll assume it starts at 120 seconds. So it'll count down from 120 going down to zero. And I'm also going to love.graphics.printf. And printf is what allows us to format the printing so we can center things or align them. In this case, we're going to right align them. I going to say love.graphics.printf0000. Eh, just zero for now. And then we want to right align this. And so printf if you're using it in your aligning something within some bounds. It'll start at zero. It's going to also start at zero height. It's going to take up the virtual width as its center, its alignment zone, you can sort of think of it. And then I'm going to not center align it but right align it. And then if I run this, you'll see. And it's somewhat difficult to see on the stream there. But I do have a zero on the right side of the screen at the top right. And that's going to be where we have our score, the user's score as they play. Cool. So that's working. Well, we'll wire those together at some point in the near future. Now, what I want to do, let's go back to Minesweeper. The actual graphic and let me zoom in just a little bit here. I want to have a few things happen. So there are a few things we can look at that are fairly simple, especially if you've watched prior streams. One thing is we're going to need tiles. We're going to need two separate tiles it looks like, at least two separate tiles. So we have one tile and this is a pretty poor resolution to be looking at. But we have a tile that is unrevealed. So it's this tile here. So in this case, you can see that it's a little bit three dimensional-looking. And the way they accomplish this was they chose a center color of some sort of pale gray. They used a lighter color for the left edge and a darker color for the right edge. A very simple way to get a three dimensional look for your game. Now what size would this be? This would probably be maybe 10, 12 pixels. If you use an eight-pixel font we could probably achieve this. Let's try 12 pixels. So what I'm going to do is, I use a program called Aseprite, which I like. And Aseprite is a drawing application. So kind of similar to Photoshop, kind of similar to something like GIMP. It's used mainly just for drawing 2D sprite work for games like this. To answer pudavodatel, are you sitting or standing? We are thankfully sitting. I wouldn't want to stand for three hours. Tuxman, can you use emojis? RODRIGO SANCHEZ: We were talking about table indices. COLTON OGDEN: Oh, sure. Probably not. Probably not in Lua. Maybe in Swift, because I think you can do that in Xcode, you can use emojis in the actual programming language. Not in this programming language. Greetings from Germany. Love your video, says sensotouch3d. Thank you very much. Appreciate it, senso. [INAUDIBLE] says, think this channel is cool. Definitely need to fill in some info below the stream with links, schedule. I agree. I'll take a look at doing that as soon as possible. We have to kind of formulate a lot of that stuff. But yeah, you're absolutely right. We want some behind the scenes video. Show us what it takes to stream. I would watch it. It would be cool, says [INAUDIBLE]. Yeah. We could potentially do something like that. I'll ask David and our production team if there'd be something they'd want to do and we could maybe do something like that. Have like a stream of the stream going on in the background. Little bit of stream-ception. Back to the thing here, I'm going to create a new sprite. And it's going to be 12 by 12 pixels. So let's do that. So this is going to be very ultra tiny. Can I zoom in like that? I can. Ultra tiny. Even though this looks large right here, it's actually going to be a very small drawing. I'm going to choose this pale gray. Like we said earlier, we need a pale gray, a light gray, and a dark gray. So I'm just going to fill this in and I can actually do this with the fill tool here. I'm going to go to my pencil. I'm going to choose a lighter color. So I could choose white, but that's a little obnoxious. I'm just going to go up to here and choose that. And then again, it was kind of like this on the left going down. And then a darker gray, so something like this on the right. And you can kind of see that, actually, in the preview here. It may be a little bit difficult to see, but when it's shown at smaller scale, it really does look quite three dimensional. Now, I'm going to save this as a PNG. Not a PCC, a PNG. I'm going to go. And it's already set to stream. In our tic-tac-toe stream, we made our Xs and circles from scratch. I'm going to go up to streams, into Minesweeper. And I'm going to create-- it looks like I don't have the appropriate folder here. I'm going to save it in here and then I'll move it in the Finder. I'm going to say, this will be tile.png. It'll just save it as a PNG. So if I go back to my Finder, notice that we do indeed now have a tile.png. And now, what you see here illustrates what filtering is. So we saw very clearly in our actual Aseprite program when we were doing the sprite artwork, that it was crisp, you know? We saw the individual pixels of our 12 by 12 image. But Mac OS and Windows OS, I believe, by default apply filtering to all image previews just so they look kind of nice as you're looking through all your stuff. But when you're looking at something very small like this, like a 12 by 12 pixel, it looks blurry. It looks like it's actually at a higher resolution than it is. And this is what filtering looks like versus nearest neighbor or point filtering or no filtering. So this is a perfect illustration of what that is. Now what I said before is I'm going to move this. And what we generally want to do is have a separate folder for your graphics. So I'm going to go ahead and I'm going to take tile.png, put that in my graphics folder. I now have a tile that I can draw on the screen. To illustrate that this looks OK when drawn in a grid, I'm going to, in my main.lua, do a simple loop. I'm going to say 4y gets one until 10. Do, just put a space there before the end of that. Actually going to put a space between push start and the rest of the code just for style. This is a nice thing I like to do stylistically, just to illustrate semantic difference between the different things you're doing in a large chunk of code. So splitting out the fact that we're starting with the push rendering and then the font rendering, and then separately our loop that's going to draw all of our tiles. And then separate from that, the actual closing of the push block, that's a nice way to space out your code. Makes it easier to read and understand what's going on and to separate things semantically in chunks. So for y, it's 1 to 10. This is a nested loop. I'm going to say 4x is 1 to 10, do. And what I'm going to do is draw this. So I'm going to say love.graphics.draw. And we have nothing to draw yet. So that's another thing that we want to do. I'm going to go into dependencies.lua and I'm going to say G textures. And so typically you'll hear 2D graphics referred to as textures or rasters, in this case, textures. So I'm going to say, G textures is equal to, and we'll call this tile. And we'll say love.graphics.newimage. And this is going to be graphics tile.png. So just loads that tile.png that we created from our Aseprite. And we put it in a folder. Just going to load it from disk in that graphics folder just like it does with the font file, the ttf file, as well. I'm going to say love.graphics.draw, G textures. And I'm going to say tile. I'm going to say-- and we have to draw this at a particular xy location. So I'm going to say x - 1, actually no. I'm just going to draw it at x, actually. I'm going to say x times, and then in constants.lua I'm going to say tile size equals 12, right? Try to take magic numbers out of your source code as much as you can. You could say we're using it here, debatably. But this is just to illustrate something. We will get rid of this. But we will not get rid of, for example, the tile size. We're going to use that a lot. I'm going to say tile size, y times tile size. And that should actually just work right off the gate. And if I run this, you indeed can see that we have sort of a Minesweeper-like grid being drawn. And it's offset a little bit because we're drawing this not at 00, we're not starting at 00 because we're actually starting the xy rendering at one. Notice that y and x start at 1, they don't start at 0. So I'm actually drawing relative to the top left. I could say x - 1 times tile size and y - 1 times tile size. And what this will do is, this will actually drive, based on the top left corner. And this is nice and clean and flush with our window. But it obscures the timer that we put earlier in our actual grid. When we get to the game itself, when we lay everything out the way we properly want to, it's not going to be at the very top left. It's going to be kind of shifted down, kind of centered in the game world. So, yeah. Looks kind of nice already, right? RODRIGO SANCHEZ: I like it. COLTON OGDEN: Very simple, too. Just a very simple idea. Something that you see a lot in sprites and in UI. Just make the far left edge of something a light color, it's kind of in the same palette as your central color. And then make the far right a darker version of that same palette. Let me make sure that I caught up. Is CS50 available somewhere? The CS50 website is all about some event and that is it. And then looks like, yeah. Tuxman kindly put in cs50.edx.org. Got to go. Thanks for the great stream, as always. Learned about better folder hierarchy from my future project. Awesome, thanks tuxman. Appreciate you tuning in. Glad that you were able to pull something away from today's stream. Cool. So we have our grid being drawn. We have a little bit of a tutorial on drawing some basic sprite stuff. That's not the only tile that we want, though. Because if we look at the image here, we can see that when we do click a tile and that tile has been depressed, it's actually very similar to our other tile. But it doesn't have a light edge any longer. Now what it has is just a dark left and top edge and then nothing on the right and the bottom. And so it's very simple. We can do the same exact thing that we just did. I'm going to go back to Aseprite. I'm going to copy this. Going to draw this here. So keeping the left edge intact, and then I'm going to take this dark color that we used before. I'm just going to make that the left edge just like that. And then I'm going to save that. Not what I wanted to do. Whoops. I'm going to save that. I'm going to reapply. I'm going to command shift save that to create a new image. And I'm going to call this tile depressed, like that. And actually, it saved it again here. So it didn't actually overwrite the file in our graphics folder. But even still, you want to watch out for accidentally doing something like that. So tile depress, put that in the graphics. I'm going to go ahead to delete that. Now you could put those onto a separate sprite sheet. Sorry, you could put both of those together onto one sprite sheet. But given that they're so small and we're only going to really have two separate tiles, we're going to see that we're actually going to draw everything else on to these tiles. We're going to basically draw the tile and then layer on top of that some text or an icon. There's no real need or reason for us to go through the effort of creating a sprite sheet. And then additionally, splitting that sprite sheet in code. We can avoid doing that in this case. A 3D-looking unimpressed grid would do. Not sure what you mean, because that was kind of the goal of what we just accomplished, Bhavik, if you want to elaborate on what you mean by that. OK, so cool. What I'm going to do now is I'm going to-- let's test to make sure this actually works. So I'm going to create a grid. I'm going to create an empty grid. I'm going to say 4y is equal to 1 till 10. Do 4x is equal to 1 do. And then I'm going to say table.insert into grid in another empty table. I'm basically creating a 2D table here. And then I'm going to say, table.insert into grid y, which will now exist by the time this happens, because we will have just inserted a table. I'm going to insert into that either a one or two. So in this case, math.random, two. And another important thing that we should do, actually, is seed our random number generator. So going to say, math.randomseedos.time. If you're not familiar with what this does, this just seeds our random number generator with our OS time to always be different every single time we start the game. So this means our random number generator will function differently every single time we play the game. So we've created a 2D grid here. Every single index. So this is going to be mapping to our actual tile grid. Every single index in our 2D grid is going to be either a one or a two. And so what I can do down here is, I can say grid at yx is equal to one and this or gtextures tile depressed. RODRIGO SANCHEZ: And underscore instead of the hyphen for tile depressed? COLTON OGDEN: No, no. Because what we're going to do is I'm going to go into dependencies. But I haven't done this yet. I'm going to create another index here called tile depressed. And this is not really a formal convention. I would say I've seen it more than I've seen this for indices, at least in the context of Lua. But my experience might differ from your experience. I'm not sure whether there's a formal standard for this. But I typically like to use hyphens to be my indices for tables in strings. And I'm going to say love.graphics.newimagegraphics tile depressed. And underscores are more of a convention for actual files. So there's kind of a dichotomy here, which you could argue could lead to a source of bugs for people assuming that it will be underscores or for developers even assuming it'll be underscores. But there's semantic value, I guess, in knowing that that's the table key versus a file. So maybe that's OK. But in any case, I have this implemented. And now if I render the game and it's all randomized, you can see that we do have a grid of tiles and we do get this nice faux 3D look, right? You get that tile. We sort of tricked our mind into thinking that that's lower or that tile is higher than that tile on the left. So very simple things that add up to make a pretty nice-looking UI for our game. Now, do we have any more comments that we should read? RODRIGO SANCHEZ: Just some chatting. COLTON OGDEN: OK. Cool, cool. So, Bhavik-- or, sorry, Bellocure has kindly pitched my game dev course. Hevron is saying, that looks amazing. Bhavik's saying, cool. I agree. This is super cool. And I want to say, I have never implemented this before. I just happened to know these puzzle pieces that I've picked up from working on this game, working on this game, working on this game. Making observations about the game that I'm trying to implement. And then actually orchestrating that in code. And you can sort of predict how things are going to look and behave the more experience that you get. And so I went into this knowing what the outcome of this would be. But even I, looking at this, I'm like, wow. This actually looks pretty cool. I think this is pretty awesome. One thing we could do, we could actually darken the background just a little bit, just a shade to give it even more of a 3D look. So I think I might do that. I'm just going to sample this color here. And then I'm going to bring this over just a little bit. And then fill that in. Save it. And what that will have done is it actually created a new tile depress there. I'm going to replace that one there, re-run the game, and now we have an even more exaggerated three dimensional look to our grid. So cool. I'm enjoying this so far. This is moving along. Now it's not centered, which is kind of a problem. So what I probably want to do is take care of that. So why don't we go ahead and do that. So in order to center it, what we basically want to do is effectively start drawing it at the x point that is the half point of our virtual width minus the total size of the grid. So we take this whole virtual width, we subtract from it the total width of this, which will give us this space here. Then we divide this space by two, start drawing at that on the x-axis, and that will have the effect of drawing this grid. Is that the case? No, we actually have to divide this by two as well and shift that to the left. I might be getting myself confused here. No, that should be right. Yeah, that's the margin. Yeah, that's the margin. That should be correct. I could be mentally having a little bit of brain fog at the moment. But we're going to try that. So I'm going to say, local. We can actually pre-calculate this. So I'm going to say local padding is equal to virtual width minus the total grid size, which would be-- so another thing we should do, and actually this is important, grid size or rather, grid width. And this will be 10 and then grid height will also be 10. And we can change this. The way that we're actually creating this right now, we can have this be programmatically centering. Because we're going to take this into consideration when we actually figure out the margin of our game or the padding of our game grid. So I can say, virtual width minus grid width times tile size, right? So that'll be the number of tiles and the width of the tiles. Sorry, the size of the tile which is 14 pixels and then multiply that by the number of tiles in the grid. So that'll be the number of pixels wide that our grid is in total. So I'm going to say-- and this will actually still-- because of the order of operations work the same way as the parentheses, but it lets us kind of keep track of what we're doing a little bit easier here. So I'm going to say virtual width minus the grid width times tile size. And then I'm going to divide that by two. And this will actually be more appropriately named as left padding or left offset, I should say. So this will be, again, our padding between the grid and the virtual width's difference of that. And then divide that by two. And then if I say left offset plus where we want to draw the x and then I render this-- oops, I screwed that up. OK, so that's not working. Virtual width minus the grid size. Is it this? No, that's the same thing. OK, so we have the-- let me just make sure I'm thinking about this correctly. We have the virtual width. We need to take the difference between the full grid, which will be something like, what, 140 pixels? Wait. We have virtual width minus that. RODRIGO SANCHEZ: Each tile is what? How long? COLTON OGDEN: Wait, does it actually need to be virtual width minus that and then we're minus that? That's what it is, OK, I think. I might be incorrect. It looks roughly correct. Basically, I was taking the-- if we were to get rid of that-- wait, am I not dividing it by two anymore? RODRIGO SANCHEZ: No. COLTON OGDEN: So the virtual width, these always gets slightly-- depending on what problem you're trying to solve can get a little bit hairy. So we're taking the grid or rather our virtual width minus the grid, which will be that amount, right? So virtual width minus-- let's figure this out. That divided by two, right? So will that work? There we go. OK, got it figured out. So the virtual width minus the grid's width in total. And then we divide that whole thing by two. So I was dividing it by two before actually subtracting it, which was causing it to behave inappropriately. That is completely centered. You can tell based on the untitled right here. And then I can draw this, shift it down on the y as well. I'm just going to make this arbitrary. So top offset, I'm going to say that's, let's say 40, 40 pixels. And then I can say, top offset plus that. Cool. So now it's completely centered, everything looks pretty good. Now what I want to do is to be able to choose a tile with the mouse, which is what Bhavik Knight said before. And let me just make sure I'm keeping up with the chat here. Rodrigo nods approvingly. Windows fine for programming? I see many developers use Unix-based systems and it confuses me a little bit. Yeah, Windows is fine for programming. Unix tends to make it a lot easier to program in certain contexts. But I mean, game programming is often done on Windows machines. You can program a lot on just Windows machines without too much of a headache. But I would say, I think it's a little bit easier to get into programming and to be productive and to get bootstrapped in a Linux or Unix environment just because there's a lot of tools to easily get set up, a lot of package managers that make it easy. Mac, for example, with Homebrew you can get set up really fast and it's not really that easy to do on a Windows machine in the same way. RODRIGO SANCHEZ: It's a lot of-- if you're Googling around for help, a lot of the examples that you'll find assume that you're running a Google space system. COLTON OGDEN: Yeah. Agreed, agreed. And it looks like Bhavik Knight-- yeah, Bhavik Knight had the right idea there. Yeah, [INAUDIBLE] had the idea. I was dividing by two at the wrong time, which was causing a problem. Foursunlight says, hello [INAUDIBLE],, Rodrigo, the regulars, and newcomers. Thanks for joining us. JPguy, good to see you. Cool, cool. All right. Let's go ahead and maybe break out some of this into different files. So for example, there is an idea of a grid, right? A game grid. Let's rename this actually to Game Grid because I think grid is just a little bit too generic of a class. I'm going to go ahead and this is going to be a class. So what I want to do is, I'm going to take the class library that we used last time or rather-- wait, did we use it last time? Yeah, I think we did. Yeah. No, we didn't use it there. Where did we use it? I forgot offhand. Did we use it in Space Invaders? We did. So it's an unfortunate name, Love 2D class library helper utilities. I'll say use the helper utilities for massive progression. And yeah, it's an unfortunate name and unfortunate acronym. But it's a class library that they use and that is what we'll be using. So here it is. VRLD/ helper utilities for massive progression. Within the Love 2D community, they have some unfortunate naming conventions as kind of a set of jokes and conventions. But that is the library, the specific class library that you want. I have it pre-downloaded because we used it in Space Invaders. I'm going to copy it over to my-- where is it? Minesweeper into my lib folder. And so now I have a class.lua in there, so that's going to be my ability to use object-oriented programming classes. If you don't know too much about that, I think the goal is to eventually start doing a object-oriented programming stream. I'm going to find somebody to do it. Maybe me, maybe somebody else. But suffice to say, this will allow us to put together all of the functions and data that make up a game grid and put them into one module. And that's what we're going to do. So in Game Grid I'm going to say-- well first of all, what I need to do in dependencies is say, require rather, class equals require 'lib/Class.' And it's their convention to capitalize class. And it's nice because it kind of almost feels like new syntax in Lua, and the fact that Lua is such an extensible, simple language allows you to do stuff like that. We'll say game grid class. This block comments is not terribly useful, but this might be where you include a description of how the class should behave. I'm going to say game grid equals class. And notice that I made it a global variable by not specifying local. So again, in Lua, you can say that something is local, which means that it won't be accessible anywhere outside of its scope. In this case because we defined it at the top level of the scope in this module, it can be used in any function within main.lua. But if you tried to use it, for example, within Game Grid, even though the two can talk to each other, it's not going to be accessible. Veronny, thank you very much for following and theyashbhutoria, thank you very much for the follow. So I'm going to say function game grid init. So the init is sort of the constructor function, the function that will create this object and set it up for whatever it needs to do. In this case, it will need a self.width. Let's assume that it takes a width and height. It'll take a self.width equals width and a self.height equals height. Self refers to if we have a specific instance of game grid, in particular game grid object, that particular game grid's width should be set to however we pass it with in a constructor for it, height and the constructor for it. And again, we can probably do an object-oriented programming tutorial in the future. So if this kind of goes over your head a little bit, I apologize. But kind of just take it. Think of it as this game grid, the game grid class defining what a game grid is and what functions that game grid can use. And then if we were to go in main.lua, I can specifically create an instance of game grid. I can say, for example, local game grid equals game grid width and grid height. And this right here, this is just one instance of game grid. But we could create as many as we wanted to just like this, right? And they all take the same constructor, the same game grid constructor. This is the same thing as the init function right here. Notice that it takes a width and a height. These take a width and height and it creates a unique game grid for each one of those lines of code. So yeah, that is basic object-oriented programming, I guess. It gets a lot more complicated than that that's in a nutshell it's essentially just what objects and classes are. This is a class, this is an object, and a class is kind of like a blueprint and an object is a specific implementation of that class. So in any case, we're going to be able to define all of the behavior, including the drawing and updating of our class within the game grid class itself. I could say a game grid update. And game grid render. And what this will allow us to do is put all of the code that we essentially put in main.lua to do all of the drawing and updating for our grid, or rather, just the drawing. I can take this and I can instead say something like game grid render right here. Defer the rendering to the class itself right in here. So I can say now, for one until and we don't want to use 10. We want to use self.height and self.width. And all the rest should be pretty good. Now, top offset no longer exists in this current scope. And that is left offset. So what we probably want to do is make those constants. Well, actually, no. Top offsets is not going to be a constant. But what we can do is we can say self.leftoffset. We keep it as a variable within the game grid itself. You can also argue for-- oh no, because it maintains its own rendering logic. So we want to actually keep the offset within this class, probably. So I can say take this bit of code here, delete it, bring it over here, copy that there. We do still have virtual width, grid width, and tile size references within this module because these are global constants. We put them into the constants file. They can be accessed within any module. So our game grid can make references to those variables of all those constants. And then now this works fine. The left offset is no longer a global variable. It's a member variable. This has a specific reference to its left offset. It's calculated it at the beginning of its instantiation. It calculates it as soon as the constructor is called. After it gets the width and then it gets the height, it figures out what its left offset should be. And actually, this should not be width grid width. This should be self.width. But virtual width and tile size are always going to be the same. It's just the actual width is going to be different. And then after that, we're going to make top offset a constant. So I'm going to say, top offset. Put this in here. Top offset is equal to 40. I'm going to get rid of that in main.lua. And then this is fine. This is fine. We're rendering the game grid in here. So if I were to run this, we have an issue because again, it's a new module. We're not actually requiring it anywhere. We have to do that first before we have access to it. I'm going to say require source/gamegrid just like that. And now it indeed works. All of the rendering logic and everything is included within the grid itself, within the class itself. This object has a reference to its own functions, its own data. We don't need to keep this in main anymore. That should be one of your goals when you're implementing a game, keep everything outside of your main.lua. Try to defer as much logic as you can into different modules. Keep those modules as small as you can. What this gives you the ability to do is separate your concerns a little bit. It allows you to think in smaller chunks at a time. You don't have to mentally parse a ton of information. And if Rodrigo and I are working on a game together, I can say, Rodrigo, I want you to make this game grid class and this is how it should behave. It should have a render and update function. I'm going to focus on just the architecture of the game. I'm just going to focus on main.lua right now but I want you to do the grid by yourself. And now he has the ability to do that because the grid is all taken out of our main.lua. So that's probably the most valuable aspect of it, especially if you're working on a team of people. But even if you're working by yourself, the mental assistance that you give yourself by separating things out and making it more modular is helpful in and of itself, and can boost your productivity. Let me make sure that I didn't miss any comments here. Never played Minesweeper, actually. How does it work again? Sure. So we'll end up implementing this ourself. But essentially you have a grid of these tiles that start off completely empty. And when you click on a tile, what it does is it recursively checks all of its neighbor tiles and sees if those are a number. If they're are number, then it stops recursively iterating through the board and opening tiles. If it's just an empty tile like this, it checks all of its adjacent neighbors and does a recursive, are those a number or are they empty? And opens all of those up and those all open up their neighbors until, again, they reach a terminal point. It's the base case for your recursive algorithm, right? The base case is that it's a number. And that's pretty much it. And then if you click on a bomb, which is what these numbers refer to is, how many bombs are near me? So it takes into consideration all your tiles, whether they're diagonal or not. So you can you can surmise that because this tile, for example, right here is a one, that means that there's one bomb neighboring that tile. The only tile unrevealed is this diagonal tile right here. So we can say with 100% certainty this tile is a bomb. We know that's a bomb. There's no way that cannot be a bomb, right? And same thing for this two here. This two, because there are two tiles neighboring it that are unrevealed, only two tiles that are unrevealed and because this number is a two, meaning that there are two bombs neighboring this tile, we can say with 100% certainty that both of these tiles are bombs. This three, same idea. This three, it only has three neighboring unrevealed tiles. Three bombs, therefore, all of those are bombs, right? And so that's the gist of the game. That's the game loop. Your goal is to-- using, is it induction? Deduction? The Difference RODRIGO SANCHEZ: Deduction? COLTON OGDEN: Deduction. I always get these two confused. You're supposed to surmise whether tiles that you haven't clicked on are bombs or not. And you're essentially trying to find bombs, you're sweeping for bombs. It's just a number game. It's similar to Pick Cross, which is another number game. Using number hints you figure out which tiles you can pencil in or not. RODRIGO SANCHEZ: I like twintowerpower's rule about playing the game. COLTON OGDEN: You press the grid and hope the bomb goes off, I think. That's a good heuristic for playing, yeah. Lot of greetings going around. Others are saying hello. JPguy was saying hello to everybody. Yawn. Cool. Unpress all the mines without triggering the bomb. [INAUDIBLE] thinks the naming funny for the library we referred to earlier. Cool. Give it a little hug. Go online. Yeah, that's true. You can probably find a version to play online really easily. How about on next stream we try to build a game called Dangerous Dave? Fun little game to play. Lots of childhood memories. Dangerous Dave. Hopefully my safe search doesn't give me any issues. OK, interesting. Dangerous Dave. So it's kind of like a simple platformer. And you collect gems and you find the door? Is that the gist of it? RODRIGO SANCHEZ: Looks do-able. COLTON OGDEN: To me that looks like the gist of it, yeah. It's similar to what we do with Mario, the Mario piece set. Little fire obstacles, we could do like a simple platformer at some point. In the spirit of Dangerous Dave or Mario or something like that. Yeah, we could do something like that. I don't know if it'll be the next stream but I'll definitely keep it on the docket, for sure. Thank you for the suggestion. You don't have to deal with one huge file of codes, says [INAUDIBLE].. Yeah. And that's a big headache. You don't want to do that. It's a pain just trying to look through and seeing where problems are and tracing them back and forth. Much easier to look module by module and then get an easier understanding of what you're dealing with as a whole, holistically. RODRIGO SANCHEZ: At least for [INAUDIBLE].. COLTON OGDEN: Yeah, yeah. Encapsulation, collaboration. Yep. Exactly. Grids corner is three neighbors, edge is five neighbors. There is eight neighbors. Bummer, man. Yeah, essentially that's-- yeah, for a corner you would only have-- well, for a corner you would have three neighbors in the corner, yeah. Five neighbors and an edge and eight neighbors anywhere else. Yeah, that's correct. Cool. All right. So again, just because I love seeing this. This is our grid. What we should do is, first thing that we should do is plant bombs, right? So what we can do is, each grid, therefore, each index in the grid needs to have a reference to whether it's a bomb or not. It needs to know that. It needs to know that piece of information. Because if we're going to need to check each tile individually, hey, are you a bomb? If not, then also if we've discovered you, we need another attribute. Have I clicked on you? Are you visible, right? So if you're a bomb, you may be a bomb and invisible. If you're invisible, maybe you're not a bomb. So maybe you have a number of bomb neighbors that it's all going to be hidden initially. But each tile does know how many bomb neighbors it has. So it needs a reference to that as well. And that number is going to be hidden until we click on it and unreveal it. So we have a few variables, flags that we want to keep a reference to. Now the game grid itself isn't going to maintain a reference to this. This is going to be each individual index in the grid, right? That's going to be an actual tile, right? And so what we can do is we're going to need to create another class. We're going to need to create a game tile or a grid tile class, right? Let me go ahead and do this. Grid tile class. This is again, useless comment block but it's a good habit to get into when you want to actually fill this out with a description or with some information about how to use the class. I'm going to say, grid tile is equal to class just like we did with the game grid. I'm going to say function grid tile init. Function grid tile update. I'm not sure if we're going to need update. We will probably want render. So the grid tile will defer all of the rendering. This is another nested deferring of functionality. We're taking from main, we're deferring the rendering of the grid to the game grid class. But within the game grid class, we're then deferring the rendering of each tile to the grid tile class. So things kind of trickle down and they defer. And then you eventually do get these spider webs of functionality. But this, again, it's easier to go down and see things at a more granular level and separate your concerns or your worries rather than have to grok through a tremendous body of code. You can sort of isolate the problem a little bit more easily. RODRIGO SANCHEZ: It's like a company. COLTON OGDEN: Yeah, it is. It is. Essentially, yeah. It's like having a president that defers to vice president or maybe a CEO or board of directors that defer to a CEO, CTO, COO. Those have teams. The CTO might have a technical team. The CEO might have a marketing team or a financial team. Those might have interns, they might have full-time staff. Those might be managers, which have interns, which have full-time staff. You do get this hierarchy a lot in the real world. And it maps well to games for the most part. Now there are people that say that object-oriented programming is horrible and we should use functional programming. We should use other paradigms. We should use logical programming. But however you can think of the problem and solve it more easily, that's essentially a step in the right direction. Whatever gets your software done better and faster, better also being bug-free, more maintainable. Faster isn't always necessarily the best thing. Maintainability is also very important. Whatever gets that done ultimately is good in my opinion. So what we want to do is render the actual tile. And now the tile is going to maintain a reference to its xy. So what we can do is I can say, it's going to need a reference to it's xy because it's going to need a reference to-- is it necessarily true? It might not need to reference it's xy, actually. We might be able to get away without doing that. It will minimally need whether it's a bomb. So what we can do is, we could just set it in here. So bomb. So if the bomb is equal to, rather, better naming is bomb. Self.isbomb. So we've made it a Boolean variable, is, we've semantically changed its purpose. So self.bomb could be anything, but self.isbomb, now we know that that should be a Boolean expression. And we'll just set that to the value of whatever is bomb is. So in the constructor for the grid tiles, we're going to actually determine whether something's a bomb or not. I'm going to say self.hidden or rather, is hidden is equal to true. So every tile, when it first gets created, is going to be hidden by default so that players of the game have to click that tile before it gets visible. And then we're going to say self.bombneighbors, something that you should not do in real life. Self.bombneighbors is going to be equal to, let's say zero for now. RODRIGO SANCHEZ: And a convention that I was introduced to was, just like you did, bomb or isHidden for Booleans for counter variables, kind of like num bomb neighbors or something like that. COLTON OGDEN: Yeah. We could do that, too. Num bomb neighbors. So it's a good semantic clarification of the variable. So self.numberofbombneighbors is equal to zero. And now what that's done is that's taken this away from ambiguously being a verb to being a noun. So now we know this is actually a noun 100%. So number of bomb neighbors because we set that to zero. And so what we can do now-- RODRIGO SANCHEZ: Bomb neighbors could be a function. COLTON OGDEN: Yeah, exactly. It could. It could. Something that maybe recursively detonates all of your neighbor tiles. Wouldn't work in this game, but you could think of something like that. So grid tile render, this essentially will outline the code, more or less. But what is it going to do is it's going to basically say, if self.isHidden, then we just want to draw the unrevealed tile, right? So I can say love.graphics.drawgtextures and what this should do is be given an x and a y. Because we're going to call this from main. We're going to call this from the grid. We're going to offset this value in a nested for loop within the grid class and pass in this value as an offset to this so that this knows where to draw at the correct location. So I'm going to say g textures tile and this is going to be just a plain tile. And then I'm going to draw this at xy. And then else, if it's not hidden, I want to say if self.isbomb then love.graphics.drawgt exturestitledepressedxy. And then on top of that, I'm going to say love.graphics.drawgtexturesbombxy. So we're doing two draw calls here. We're saying, first draw the tile depressed. And then on top of that, draw the bomb. And so what we need to do is we need to actually draw the bomb sprite, right? So let's go ahead and do that. I'm going to say what we should probably do is draw all the numbers, too. So in here what I can do-- what we could have done this whole time, actually, is just draw the bomb onto this and save it separately. So I might actually end up doing that. And we can make it one draw call that way. I'm going to just-- let's just draw a bomb here. So I'm going to say boom, boom. Boom, boom, boom, boom. So I'm a terrible sprite artist but hopefully this works OK. We want that. So it's probably something like that. And normally I would do that and then I would probably make this a lighter color. That's too light. And then I would say probably something like that. And then maybe like a bright color, like a red or an orange. Yeah, something like that. So it kind of works. You can see, you get the gist of it. Maybe even for a little bit of decor here we can put a little shiny thing like that. So you can tell kind of what it is. But that's going to be our little bomb image. It looks almost like a pouch more than a bomb. But you get the idea. Would it make more sense-- RODRIGO SANCHEZ: There's a little shout-out in the chat. COLTON OGDEN: Would it make more sense if I did something like this, actually? If I did like that? Does that look more like a bomb? RODRIGO SANCHEZ: That looks way better. COLTON OGDEN: OK. Cool. So that's an actual bomb. I'm going to save that in graphics. I'm going to save that as bomb. And I'll read the chat here in just a second. Bomb.png. I'm going to erase all of this stuff here. Sort of overwrite it. And I think it might be easy just to ultimately draw all the numbers as separate sprites. OK, cool. Go ahead and figure out where I left off. OK. So [INAUDIBLE] is saying, the React Redux course you did last year helped me a lot. Now I'm working as a React name developer full-time. Thank you a lot. Oh, that's awesome. So that was Jordan's course. So Jordan Hayashi did a React native course on edX. Super cool course. Talked about React with the React basics, JavaScript, advanced JavaScript, and then React Native. So it's super cool that you got a job, [INAUDIBLE].. And then also that you were able to conduct a workshop, that's incredible. So awesome. Congratulations. Good job. Keep working hard. I think it's mobile.edx.org. I believe that should be the course that you want to link to. Oh, wait. Is that right? Mobile.edx.org? Sorry, cs50.edx.org/mobile? Yeah. Sorry, my apologies. Oh, yeah. And Martin kindly tossed in the chat there. Yeah. I screwed up. Ignore my link. Mobile.edx.org is not anything. That's a fictitious link. It's fake news. I'm going to now segue from drawing the bomb. What I want to do is I want to draw all of the letters that make up the tiles that have been revealed already. So let's go over to here. I'm just going to go ahead and choose a random color. So and this may be terrible. So let's go ahead and do something like that. RODRIGO SANCHEZ: That's pretty good. COLTON OGDEN: Something like that. Thank you for your encouragement. Much appreciated. RODRIGO SANCHEZ: It's better than the bomb. COLTON OGDEN: Yeah. I think that's OK, right? It might be slightly off-center. No, even if we shift it to the right, then it'll be off center on the right side. So it's fine. But that's OK, because this is also dark. OK. If I do this, then move that one over like that. RODRIGO SANCHEZ: I think even though it looks strange in the zoomed in version in the smaller version, when you shifted it to the right it looked centered. COLTON OGDEN: When I shift it to the right in the smaller version? RODRIGO SANCHEZ: Yeah. I was looking at the preview. COLTON OGDEN: Like that? RODRIGO SANCHEZ: But it's kind of hard to tell. I don't know. I think either way it looks kind of off. COLTON OGDEN: I think this looks OK. For the sake of speed we'll go through this. And I'll just call this 1.png. And then deselect. And I want to make these all different colors. So let's go ahead and just erase this. So two was red, I believe. No, two is green. Two is green. So I'm going to go ahead and choose green here. What does the two look like? OK, something like that. So I'll say like-- this is going to be terrible. RODRIGO SANCHEZ: Got to start somewhere. COLTON OGDEN: Yeah. So let's see, how does that compare? So it looks like it goes around like that. Goes like that. Great. RODRIGO SANCHEZ: I mean, it's definitely legible. COLTON OGDEN: Yeah. It's legible. I mean, it looks a little bit like trash. RODRIGO SANCHEZ: It looks like a circle, like a cobra. COLTON OGDEN: It does a little bit, yeah. There. That's a little bit better. RODRIGO SANCHEZ: Yeah. That looks a lot better. COLTON OGDEN: OK. RODRIGO SANCHEZ: This one seems more shifted down. COLTON OGDEN: It does, yeah. I don't want to spend too much time on it. RODRIGO SANCHEZ: That's good, though. COLTON OGDEN: I'll try shifting it up. There we go. RODRIGO SANCHEZ: Looks a lot better. COLTON OGDEN: Cool. And then I need to-- I'm not an artist at all by any stretch of the imagination. And I'm sure that that's very clear. But if you are a single developer working by yourself, you will sometimes have to do something like this. And so using simple techniques that don't require you to be super artistic like the three dimensional technique we looked at earlier, that's kind of a good way to make your game look good without going too crazy with the artwork. Emulating things that you see that are fairly simple, that's kind of the best way to go about it I think. Three's red. So we'll do that. OK, so how does that look? I mean, I know what the number three looks like. But for the sake of emulating the sprite artwork. It's acceptable, right? RODRIGO SANCHEZ: Yeah, yeah. COLTON OGDEN: Do I want to shift it up? Does that look better? RODRIGO SANCHEZ: To the right a little bit. COLTON OGDEN: We'll go with that. We'll take that. If I were doing this appropriately-- [CLAPPING] --I appreciate it. What I would be doing is ensuring that all of these glyphs are the same size and therefore could be centered uniformly. But I'm not doing that just for the sake of speed, just because we have-- oh, crap. I saved the wrong one. Shoot. RODRIGO SANCHEZ: Uh-oh. COLTON OGDEN: So what I'm going to do, yep. RODRIGO SANCHEZ: And you can save the other one. COLTON OGDEN: Save that and then redo that. And then that's it, right? OK. And then save that one as three. RODRIGO SANCHEZ: The magic of undo. COLTON OGDEN: Undo is a beautiful thing. OK. I apologize if this is a little bit slow. RODRIGO SANCHEZ: Actually, people are commenting that they like this. COLTON OGDEN: Oh, OK. cool. What do I-- so I need four. What does four look like? Four is like a purple, it looks like. I can't really tell. Well, we'll go with that. So I'll say this is four. And then four looks kind of like-- so they do like the-- RODRIGO SANCHEZ: Are they doing the corner? COLTON OGDEN: It's going to be something like this. RODRIGO SANCHEZ: You're doing the nine four. COLTON OGDEN: Yeah. Yeah, exactly. Looks kind of like trash. I mean, it looks actually pretty similar to theirs. Maybe I will get rid of this. Move over here. Go like that. I'm OK with that. I think that looks OK. It's not great. It's not perfect. it's not beautiful. But it works, right? RODRIGO SANCHEZ: Yeah, I think that looks good. COLTON OGDEN: Does that look OK? That looks good. OK. RODRIGO SANCHEZ: And give some more personality. COLTON OGDEN: Yeah, exactly. Trash sprite work for the win. What does a five look like? Let's see. So five is like a burnt orange or like a dark, dark red. So I'll do that. So go here. And get rid of this. I already used this color. So I can just use regular orange. So it'll be five. So five looks like, OK, it looks basically like an S in this case. So I'll do something like that. That looks actually, that's actually really hard on the eyes. RODRIGO SANCHEZ: The color? COLTON OGDEN: Yeah. It needs to be darker. RODRIGO SANCHEZ: I think theirs was a lot darker. COLTON OGDEN: That's hurting me a little bit. OK, we'll do something like this. That's better. Starting to get light-headed looking at that color. OK. I mean, you could just use that as is. It's not fancy. It doesn't have any fancy glyph work on it. But it works, right? You can tell that's a five. RODRIGO SANCHEZ: Do you think the bottom and top should be thicker? Like two lines, maybe? Nah. COLTON OGDEN: Probably not. It looks pretty similar to the other one. Just has a smaller resolution. So we'll do that. Yeah, that should be fine. Something like that or like that or like that. RODRIGO SANCHEZ: I think it looks fine. COLTON OGDEN: You think it's good? Cool. So that's five. RODRIGO SANCHEZ: How many are in it? COLTON OGDEN: There are eight, I believe. RODRIGO SANCHEZ: Great. COLTON OGDEN: OK. We're getting there. What is the-- this is someone else's version, too. That's different. Different version of it. Where is a six? Come on, six. There's a seven there. Oh, six. OK, so six is kind of like a blue green. Holy crap. RODRIGO SANCHEZ: Wow. COLTON OGDEN: That is-- RODRIGO SANCHEZ: --illegible COLTON OGDEN: That an insane-- that looks like a map. RODRIGO SANCHEZ: Yeah. I was about say, like Civilization. COLTON OGDEN: Looks like Florida a little bit. Man, OK. That's insane. That's some next level Minesweeper right there. I mean, if we did the algorithm correctly for generating the map-- RODRIGO SANCHEZ: We should be able to. COLTON OGDEN: We could theoretically make this and it would work just fine. It to be kind of slow during the initial. Because what we're going to have to do is we're going to pass over our entire grid. And the grid, it's going to basically need to check every single tile for a bomb. And it's going to check every tile for how many bombs are around that tile to give it the number. RODRIGO SANCHEZ: The question is whether we would trigger a stack overflow. COLTON OGDEN: Doing what? RODRIGO SANCHEZ: Like, if the map got big enough, right? The number of tiles. Or not? COLTON OGDEN: Oh, for the recursive. RODRIGO SANCHEZ: Oh, wait. The map isn't recursively generated. It's just checking. COLTON OGDEN: The map's not recursively generated, no. RODRIGO SANCHEZ: Unless it just so happened that there were bombs every so often and so you ended up taking the entire map or something. I don't know. COLTON OGDEN: Yeah, and even then, I don't think that there would be that many calls. Like if you multiplied-- I mean, I guess it could theoretically be a stack. RODRIGO SANCHEZ: It would be very unlikely. COLTON OGDEN: Yeah. But theoretically I could see it happening, yeah. All right. So six will be a sort of a weird greenish blue color I think. This color. Maybe? So six. This might be actually a little bit too dark. RODRIGO SANCHEZ: Their six looked pretty light. COLTON OGDEN: We'll try this. Looks like a G. RODRIGO SANCHEZ: G. COLTON OGDEN: Move that over here. RODRIGO SANCHEZ: The four and the six. COLTON OGDEN: That's a pretty good six. RODRIGO SANCHEZ: That's better. COLTON OGDEN: I'm proud of that six. I'll say that. K, come on. Six, there we go. Let's go ahead and make seven. What color was seven? Seven was like a-- RODRIGO SANCHEZ: Well, you did the six in the seven color, I think. So you could do the seven in the six color if you wanted. COLTON OGDEN: I'll do this as a seven. Why not? RODRIGO SANCHEZ: Seven should be-- COLTON OGDEN: Not like that. Try that one more time. And then boom, boom, boom, boom, boom. It's too slanted. RODRIGO SANCHEZ: Honestly, I draw my sevens like straight. But that actually looks-- that's a good. Do you want to be consistent with the-- COLTON OGDEN: Yeah. Which means I need to start up here. Actually, I can't fit it. Oh, I guess that works OK, Right? RODRIGO SANCHEZ: Yeah, I can see it. COLTON OGDEN: It's a big seven. It's bigger than the other numbers, but I'm not too picky at this point. I kind of just want to finish making the numbers. Eight is-- I guess it doesn't really matter what color we choose as long as it's different, right? RODRIGO SANCHEZ: True. COLTON OGDEN: Can make eight a yellow or something. Maybe something like, yeah. Like bright yellow. RODRIGO SANCHEZ: Like, this is really bad. COLTON OGDEN: Yeah. RODRIGO SANCHEZ: You got really lucky. COLTON OGDEN: I hope it's not painful to look at. I guess it's not terrible. OK, so boom boom. Whoops. Boom, boom. That's a pretty solid eight. RODRIGO SANCHEZ: That's probably-- COLTON OGDEN: The best number so far. It's a shame because you probably won't see it that often. Again, apologies to the people on screen that are bored watching me make numbers. RODRIGO SANCHEZ: How to make numbers in CS50. COLTON OGDEN: Yeah, I know, right? Purple, we'll make this nice bright purple our nine. RODRIGO SANCHEZ: That looks really good, too. COLTON OGDEN: It's pretty solid. RODRIGO SANCHEZ: But I thought you said there was only eight. COLTON OGDEN: Oh, right. You can't have nine neighbors. That's silly. OK. RODRIGO SANCHEZ: Such a shame. Looked so good. COLTON OGDEN: I got into a loop, into a routine. OK, cool. So that's good. We did all that. That's beautiful. We made all of our images. For the sake of time, I'm not going to put them on a spreadsheet and then do a programmatic like indexing of the tile table in order to do it. I'm just going to load them all as separate textures. Where do I want to be? I want to be in here. So I'm going to go over here. And then I'm going to, one at a time, index them with the string one, two, three, four, five, six, seven, eight, nine. Although, I could do it with just the number itself and then that way I could index into the table with that number when we look at it. Or I can just two-string it. Actually that's fine. OK, cool. Love.graphics.newimage graphics/1.png and then I'm going to kind of do this. So that will be two, three, four, five, six, seven, eight. Cool. So now we have eight index indices into our grid there. Bomb as well. We got to do bomb. That's important. So bomb is going to be equal to love. Love.graphics.newimagegraphics/bomb.png. Cool. Go over here to-- should I run it? So we didn't actually set the thing to bomb, right? So what we can do is, in our game grid, so another one of the things that we need to do is instead of doing this four loop where we draw just a statically-allocated grid of numbers in advance, or rather numbers that map to textures via this ternary statement, what we should do is actually instantiate all of the indices in our grid. So what we can do is I can say, self.grid equals that. And this is basically taking what's in main.lua and putting it into here. And actually, grid is still referenced as a global variable from main.lua. So that's why that's still even working. But now I can say 4y is equal to one until self.height do. And this allows us to actually support rectangular grids. So I can do 4x is equal to one self.width because now those numbers are separate. Then I can say-- what we also need to do is table.insert into self.grid a new table because it's a 2D table. And then I'm going to do table.insert into self.grid at y. Not a number one or two, but rather a new grid tile here. So I can say grid tile. And then in the grid tile is where I can say local is bomb is going to be equal to math.random2. So a 50% chance in this case, which is way too high, I think, but just for demonstration. And true or false. Yes. And then I'm going to say is bomb right in there in the grid tile, because again our constructor takes in it is bomb variable and Boolean to be true or false. So what I'm going to do then is-- and in this particular instance, I'm also going to randomly determine-- I'm going to illustrate that not all depressed tiles need to be bombs. So what I can do is I can just make it revealed as well. So I'll make whether it's revealed or not a Boolean, I guess. Or rather, what I can just do is say local is revealed is going to be equal to math.random2 and true or false. So now it's not only a 50% chance that it's a bomb, but a 50% chance that it's also revealed. And then I'm going to say-- what I should do is that I should say, local grid tile is equal to this. So the reason I'm doing that is so that I have a reference to this variable, that grid tile that I'm going to add into the table. I want added and then I'm also going to say hidden is equal to is revealed, which is actually the opposite of what-- RODRIGO SANCHEZ: It's in. COLTON OGDEN: Yeah. So this should be is hidden. Giving everything it says, in this case it doesn't really matter. Either way we're just setting something to true or false at random. But the semantic of that is important to take into consideration if we're doing something more complicated. Now, this will give us grid tiles that are randomly bombs, randomly hidden, and a mixture of the two, right? So if it's a bomb, then I'm going to draw. Then it's a bomb. Else, right? Because if it's not hidden it could be either a bomb or not. And so I'm going to draw whether it's a bomb or not. And then eventually what I'm going to do is, we're going to have that number attribute associated with each tile, whether it's two, three, four, five, six, seven, eight. And if it's any of those, then when we draw that instead of tile depressed, basically. And draw a bomb if it's a bomb. And then if it is a bomb as well, then it wouldn't be inside render. It would be inside of update or something when we actually click it. If it's a bomb and we discover it, then that's also going to be the trigger for the game over, too. Because when you hit a bomb you get game over and then you get your score. I have not been looking at any of the chat. I apologize. So let's do that. Nine is impossible, yep. [INAUDIBLE] I got into a mental auto-pilot. Wow, there's a lot of chat. OK. RODRIGO SANCHEZ: There it is. COLTON OGDEN: OK. Cool, cool. Slytherin, the audacity. Yeah, adamantium was saying, it is super useful to see the process of making sprites. Yeah, and again, I use Aseprite but there are a lot of other tools like Photoshop and Gimp you can use to make sprites, too. It's kind of up to personal preference. Aseprite is catered specifically to words 2D sprite development. But it's also not free. So you'll want to take that into consideration. RockdayStudios, in my game developer, Godot is an open source 2D 3D game engine. No fees, no lawsuits. Yeah, I've seen Godot. I'm familiar with Godot. Thank you for mentioning it, though. Press F for two. [INAUDIBLE] Do you know what that's a reference to? I'm not sure. RODRIGO SANCHEZ: Forgot what that was. COLTON OGDEN: Apologize. I'm not sure what that means. RODRIGO SANCHEZ: Maybe that's what we were creating the number two. I don't know. COLTON OGDEN: [INAUDIBLE] says, if it's an ad, it's a good one. Yeah, I mean, people should be encouraged to mention technologies in stacks that they like. So no shame in that. I think if somebody does it over and over and over again and if it's something that's obviously a product that's trying to be sold it should be a problem. But I think mentioning Godot as open source is totally fine. And people should feel encouraged to check out those tools, those pieces of software. Cool. Instead of checking every tile, why not add a counter around the bomb object which is hidden for the player? Or was that the idea? Yeah, every tile is going to be pre-calculated so it's going to be cached. Every tile is going to know exactly how many bombs are around it. So when you click a tile and-- is that the case? It's going to essentially recursively look at all of its neighbors. If it's a bomb, it's not going to reveal it. If it's another tile with a number that's not a bomb, it's going to reveal that tile, which will then reveal its other tiles. And if it's empty, it will reveal that tile and also trigger the recursive thing. So empty and numeric tiles have the same recursive functionality. But if it's a bomb or an edge, that stops the recursive algorithm from continuing. And also actually-- well, no. Is that the case? Is that true? No, that's not the case. So if it is a number, it doesn't-- if it's a number, it's also an ending point. So at a number, the number will stop recursively looking through tiles as well. That's a base case for the recursive algorithm. If it hits a number tile it stops there. It just basically hits a wall at that point. Has anybody seen an eight in Minesweeper? Yeah, I feel like that would be pretty tough to actually get. But I'm sure a lot of people have seen it. We need a puzzle with a small map and huge number of bombs. Yeah, yeah, yeah, yeah. What color were you making? Yeah, troll. Yeah. Brain fart. We need a nine and a zero for the sake of completeness. Yeah, kind of feels like that. [INAUDIBLE] Minesweeper, guys, don't worry. Yeah. Yeah, the extra z layer. 4D Minesweeper, holy crap. Nine not needed. Idea. What are your thoughts on a flag is needed. What's the flag? Do you remember what the flag is for? I've seen it in Minesweeper but I honestly don't remember what it's used for. RODRIGO SANCHEZ: I haven't played it since I was, like, six. COLTON OGDEN: Yeah. If you want to let me know what the flag is for, I actually don't know. 3D would need 26. Oh, yeah it would. It would need 26 indices for a 3D, a three by three by three. Check around the tile. Not nine but a flag, a flag and an exclamation point. People are asking about validators on the web I think validators at the HTML site are still circumventable. So ultimately, you probably want back end validation. Depends. RODRIGO SANCHEZ: [INAUDIBLE]. COLTON OGDEN: [INAUDIBLE]. I like coming in here. Every time I fail to understand what's happening and that motivates me to go continue my backlog courses. I don't know whether to take that well or not, but I appreciate you coming to join us. If you have any questions along the way, definitely let us know. I would recommend starting out with the snake or the tic-tac-toe streams, though, because those are a little bit easier and they give you a good jumping point. Depends on your audience. I guess it's a bit of a mix. But if your main audience is on mobile, you could go mobile first and make it adaptable for bigger screens. Yeah, they're talking more about the mobile stuff. Press F to pay respect when you overrode the two number. Oh, I see. I see. [INAUDIBLE] Do the bombs get generated after the first click? Bombs get generated at the beginning of the map. So the whole map is completely generated at the very beginning, all the bombs, and all of the neighbors of the bombs get their numbers pre-calculated. And then you just reveal them by clicking on the mouse and recursively revealing tiles. Use the flag to make where you think are bombs, useless. Flag is for marking a bomb that is-- oh. So the ability to actually say, oh, I know this is a bomb. To mark it. OK, OK. Yeah, I'll have to think about that. I didn't think about that, actually, earlier. But yeah, that would be smart. And that would actually be pretty simple. All you would have to do is-- we'd have to go into a mark mode. And so if they click it when they're in mark mode, it just changes that. Basically turns on a flag skin for an unrevealed tile. So yeah, that's something we could do. RODRIGO SANCHEZ: I'm actually about to head out. COLTON OGDEN: OK. Sure, sure. Well, it was good. RODRIGO SANCHEZ: Yeah. Tomorrow I'll be here for the full time. COLTON OGDEN: Oh yeah, that's true. Yeah, JPguy just said, [INAUDIBLE] can check out chat while you program. But Rodrigo is unfortunately leaving. RODRIGO SANCHEZ: I'm out of time but I'll be back tomorrow. And we'll hit object-oriented programming again a little bit. COLTON OGDEN: OK, cool. RODRIGO SANCHEZ: And switch over to Python. COLTON OGDEN: Yeah. RODRIGO SANCHEZ: You said that we haven't done much Python streaming. COLTON OGDEN: Not a lot. RODRIGO SANCHEZ: So that'll be fun. All right. It's been real. COLTON OGDEN: It's been real, man. Appreciate it. Thank you very much. OK. So I do like the idea, yeah, right click. Right click can set a flag. We can take a look at that, actually. We have another hour or so left. Hopefully we can get all the features done. I mean, we have several more steps to do. This might end up being a two-part stream. And if it is a two-part stream, we would definitely do something like flags and multiple game states and all that sort of thing. We'll see how much we can get finished after this. And I'll commit everything to GitHub. And this will probably, if it is a two-part stream, it'll probably be next Monday. But yeah. Let's go ahead. So the next step we needed to do-- oh yeah, we were going to draw the tile being depressed or a bomb, depending on the situation. So OK, so it's looking for grid tile. We haven't added grid tile to our dependencies yet. So let's go ahead and do that. Requiresource/gridtile and it's not quite working yet. So let me see why that is. Oh, you know why it is? It's because we're still doing this bit of code here, which is not what we want to do. We don't want this to happen. What I want to do is I want to draw, rather, I don't want to draw anything with that. self.leftoffset plus-- and self.y offset plus yeah. What'd we do? Would we do it like that? self dot-- OK. We're going to do self.grid at yx, render, right? Because we defined a render function in our grid tile class. So these are all grid tiles now. They aren't just numbers anymore. It does take an x and a y, if I'm not mistaken, right here, which is this x and y. And we can just reuse the x and y that we have here. So self.leftoffset, blah, blah, blah. Wow, that looks great. And, OK. So we have a grid full of revealed tiles now. Is hidden equal true? Oh, you know why? OK. We didn't make is hidden actually a parameter of the function. We just kind of assumed that it did. And it's still not working. So why is that the case? Let me see. Self.ishidden is equal to ishidden. Oh, is equal to one. So this was always getting true because the result of math.random2 is always going to be a truth value. So it was always giving us true because the way the ternary statement works. And same thing with this, we need to also set that. So now it works. Cool, awesome. We see some bombs, right? Looks great. I like how that bomb ended up looking. It looks pretty good, pretty good sprite work for somebody who sucks terribly at making sprites. Yeah, I like that a lot. That's super cool. [SPEAKING JAPANESE] It's not difficult, is what [INAUDIBLE] just said. It's not difficult. I appreciate the Japanese in the stream a lot, as somebody who's learning Japanese. I would prefer to see it in an actual kana I think. But we have to set up-- I'd have to go to translate.google.com so I can actually type it. So what he just said was-- I can just do it on stream, right? Translate.google.com. My computer is set on a different account. It's set so I can type at the operating system level in Japanese. [SPEAKING JAPANESE] It's not set to type in kanji. We need to do that. So [SPEAKING JAPANESE]. Let me see what he's typed and I'll remember. There he did. [SPEAKING JAPANESE] So something like that, right? Saying it's not that difficult, though. [SPEAKING JAPANESE] Anyway, try not to spend too much time on that. Pretty basic sentence. I'm not good at Japanese at all, but I read much better than I can actually speak and understand. But yeah. Because the map is generated at the beginning randomly with correct math, turn all tiles hidden and is presented to the player. I mean, everything is going to be unrevealed at the very beginning. We don't need anything specifically math-generated. I mean, in the sense that we will be looping over all the tiles to see how many neighbors they have that are bombs, we will need to do that. And that's sort of like a pre-calculation. But that's all we're doing. Bombs are turned into flags when the game is won. Oh, that's a cool feature. [INAUDIBLE], are you Japanese? [SPEAKING JAPANESE] Would that be how you say it? [SPEAKING JAPANESE] This was the game Windows offered by default. It should not be so easy. I don't know. It's a pretty easy. I mean, all things considered it's fairly easy. I would say it's not terribly easy. [INAUDIBLE]'s got Greek. I know what Greek looks like. I can sort of read it. [SPEAKING GREEK] I don't remember whether that's-- it's been a long time. I used to know the Greek alphabet pretty well. Not any words in Greek, but-- [SPEAKING GREEK] I don't remember whether that's L or M, lambda or mu. [SPEAKING GREEK] In this case, it would be [SPEAKING JAPANESE] because the equivalent in English, present progressive tense. Correct. [SPEAKING JAPANESE] JP, do you speak Japanese? Anyway, language divergence. Oh, that's sick. Fluent Japanese or? That's super cool. Curious how many people in here actually speak Japanese. Everyone's going to start coming out and saying, oh, I speak Japanese, too. Oh, I speak Japanese, too. Nice. That's cool, man. I've only been studying hard for maybe a year, less than a year. And on and off. But I can read fairly well. N3 says [INAUDIBLE]. Yeah, I'd guess I would probably be N4. I'm much better at reading and writing than I am at speaking and listening. Friend speaks. He passed N1. Learned it with himself without any textbooks. Yeah, that's essentially what I'm doing. I'm using a little bit of textbooks but mostly through actually playing games in Japanese. It's the main reason I wanted to learn. 3DS with Japanese games. If you guys want to practice kanji together, I'm totally down. OK. Sorry, I'm spending too much time on the Japanese tidbit. It's a lot of fun but we should probably make progress on this. So we have the grid here with the bombs. Super cool, super awesome. What we need to do now is make sure that all of the tiles have the number of bombs as their neighbors and that that's correct. So what we need to do is, we need to calculate-- once our grid is constructed, basically, we need to call a function called calculate numbers, right? And this will go in here. And what this is going to do is iterate over the entire grid. So I'm doing a 2D iteration. And I need to basically look for every single tile. I'm going to look to see all of the neighbors excluding edges and corners and things like that. [SPEAKING JAPANESE] I'm a native Russian speaker and can speak a little French, as well. Nice, nice. Is asley here? Actually, I haven't seen her in the chat. So I'm kind of surprised, actually, now that I think about it. And I got to remember that it's her birthday on Friday, supposedly. So I need to verify that on Facebook. But we should all remember that asley's birthday on Friday and wish her a happy birthday. Yeah. So what we need to do is iterate over the entire grid and then for every tile, we need to look at all of the neighbors. So in this case, we say check top left, check top, check top right, check left, check right, check bottom left, check bottom, check bottom right. And there might be a more programmatic smooth way to do it. But this is effectively what the logic is. So we need to say, if x is greater than one and y is greater than one, then-- and another thing we need to do is say, local num bomb neighbors is equal to zero. We're just going to add one to this every single time that we find a bomb. So what I can do is, I can say if self.tiles at x minus one, y minus one dot, and it would be .isbomb, then num bomb neighbors equals num bomb neighbors plus one, right? So that's checking the top left. So we're minusing on the x, minusing on the y. So that's just that logic. The top is a little bit simpler of a check. It just basically says if y is greater than one, then if self.tiles y minus 1x.isbomb then num bomb neighbors equal num bomb neighbors plus one. So this is the logic involved. And this takes place for every single tile and all we're doing is essentially bounds checking all of these checks of our neighbors. So just making sure that we're not going to the left of the far left edge, to the right of the far right edge, above the top, below the bottom, that sort of thing. So, same thing for the top right. So if y is greater than one and x is less than what would this be? Self.width, then if self.tiles as this is top right, so this will be y minus one, x plus one.isbomb then num bomb neighbors equals num bomb neighbors plus one, right? Pretty simple. We're just checking left, right, up, down in different permutations and that will tell us whether a neighbor is a particular way, right? So check the left. If x is greater than one, I'll just copy this. If self.tiles at y and then this is x minus one. For the right, let's kind of do the same thing but reversed. So if y is less than self.width, then we'll do y and this will be x plus one. Bottom left is going to be if x is greater than one and y is less than self.height, I'm going to do this, do this. So not a terribly exciting bit of code here. But kind of just, this is the core of our pre-calculation. This is basically all we need to do. There's no way to be at the limits, right? E.g., self.tiles negative one or something. Yeah, that's essentially why we're bounds checking, to make sure that when we check to the right of x, that we're less than our width. Because if we are, then we can go up one index and be at the right edge at the worst case, if not to the left of that somewhere, between one and the width, right? That is exactly why we are doing all of these if statements, for the bounds checking. That's the sole purpose of these if statements. Otherwise we could just blindly do this x plus one but that's not going to work when we're at the far right side of the screen because there are no more indices to the right of that, right? So this is why we're saying, if y is in this case, this needs to be different. So x minus one, we're making sure that we're greater than one. Because if we're at one and we do minus one, we'll be at index zero, which is not a real index in Lua tables because they're one indexed, right? So that's effectively why we're doing this. So self.tiles, and this is going to be also y plus one. I have plus one, x minus one. And this is why we're doing less than self.height, right? So check the bottom. So if y is less than self.height, then-- and then we can do this. We're just checking x and y plus one. And then bottom right, which is if y is less than self.height and x is less than self.width. And then we would do x plus one, y plus one. Cool. That's good. And then now we'll have a number for this particular index in the grid, right? For that particular tile. So I can say, we've done all of that. Num bomb neighbors shouldn't be at that level. It should actually be over here. Store all bombs we see around tile. Checking on neighbors. This is kind of just to give a sense of what this particular code box is all about. So we're checking all the individual tiles and then at the end, once we've checked all of the tiles, we can then say store number at that tile. We can say self.grid at yx dot, and this would be num bomb neighbors equals num bomb neighbors, right? So now we have that. So what we can then do is say, in our grid tile, so now we're going to actually have that number, right? So we can two string that. Can say if it's a bomb, then we [INAUDIBLE] bomb. Else, wait. Do I have this reversed? I have this reversed. If self.isbomb, don't draw the bomb? OK. So I had that mixed up. I mean, it looked OK but the logic is wrong. So I'm essentially just going to do this. Boom, boom, do a little trade, little switch. Something like that. Cool. Now it's actually accurate. If it's a bomb, draw the bomb, don't draw the depression. I don't know what I was thinking. But yeah, draw the bomb. How does work right now? OK, tiles at 68. So I need to make sure that is OK. 68 self.tiles. Oh, it's not self.tiles, it's self.grid. OK, whoops. So we can fix that. I'll just go over to self.tiles. And make that self.grid. Right, cool, looks great. And then if it's a bomb-- so the depressed, this is where we're going to actually draw the color, sorry, the number for that particular tile. So I can say, local num bomb neighbors is equal to-- what would this be? Grid tile. OK, this is the grid tile. This will be self num bomb neighbors. Oh, wait. What am I doing? I'm sorry. Brain fart. So now that we have the reference to the number of bomb neighbors, what we want to do is say, draw that tile. We're going to map that to the particular tile that we loaded earlier, right? Which we stored in dependencies. And they all started with a string numerical index here. So it's a string but it's a number. So what we can do is we can say, if self.numbombneighbors is greater than zero, right? If it's greater than zero, then we want to draw one of the number ones. If it's zero, if it is zero, then I just want to draw the depressed tile as it is just by itself. So in here, I'm going to say love.graphics.drawgtextures and then I'm going to say two string, self.numbombneighbors, right? Which is good. Gtextures and then there I can say xy. And there we go. So we have our numbers. Now seeing them all together looks pretty wacky. But in theory, this should actually be working. So notice that there's this one here and it has this one tile next to it that has a bomb, right? And this two here is also kind of right here. I'm guessing this two and this two, these two have to be bombs. And there's no way we can really know right now. What we should do is just always draw them. So just check to see whether our algorithm is working. Get rid of the hidden check here. So I'm going to get rid of this right now for you. Get rid of this. We'll bring it back later but for now, I just want to draw every single tile. OK. Yeah. Wait. This five right here should be a six. Because one, two, three, four, five, six. I mean, these two are working, this three is working, this four is working, this five is not working. But this four is working. This four is not working, OK. Do I have something wrong with my top left? Is my top left messed up? These both are getting their top left incorrect, I think. I don't know. Well, this one is incorrect. There's a one and a two. This two is correct. Four is correct. This four is not correct. Oh, wait. Oh, you know why? Because each bomb may also have a number. So what we need to do is, we need to check to see if this already has a bomb. And if it does, don't even worry about numbers, right? So yeah. The bombs are-- I think that might be the problem. I'm not 100% sure. So I'm going to say if self.grid at yx.isbomb, then we're not going to store a number here. And I don't know if this matters. Yeah, I guess this wouldn't matter actually. But we can skip this logic at least. Yeah. I'm going to put all of this inside there. It's at least an optimization. So we'll do it for that purpose, I guess. I'm a little confused as to why that's not working. So whoops. So now it's only going to work if it's not a bomb. If not, self.gridisbomb. Cool. And I don't think that's going to work any better. It's actually the exact same grid. Is it? No, it's not I don't think. Yeah, there must be an issue with my logic because this four up here, right? Oh, wait. No, that's OK. That's fine. So, this five right here, right? This five right here. One, two, three, four, five, six. So there must be a subtle mistake that I'm making, maybe in my conditional logic here. So let me just make sure that that's correct. So if x is greater than one, y is greater than one, OK. X minus one, y minus one, right? Top left. The top, just the y greater than one, right? Make sure that, OK. Y minus 1x, yeah, that's correct. OK, top right. Y is greater than one. X is less than self.width. So y minus one, x plus one. OK, cool. X is greater than one, yep. X minus one. If x is less than self.width, OK, y and x plus one, yep, correct. Bottom left. If x is greater than one and y is, OK. Yep. Yep. Y is less than self.height. OK. OK. If y is less than height and x is less than self.width. OK. Oh. It's this. Yx. Let's try that. one, two, three, four, five, six. One, two, three, four, five. One, two, three, four, five, six. One, two, three, four, five. OK. I think it was just that. It was a silly-- I had put x here and y here. Should be reversed. Y goes before x in this case with 2D indexing. Cool. Awesome. Colton, consider all corners and edges first. I don't think that's necessary. Unless you're talking about this, the if statement in my conditional logic. But I don't think it matters what order you check stuff in as long as you check all of them. Live debugging, NACLeric. Yeah. No, that was good. That was a good illustration of what debugging looks like sometimes. But no, this is great, right? Now, let's go ahead and actually render this appropriately. So we can say if self.ishidden then else so love.graphics.drawgtexturestile tile at xy, right? Cool. So the hidden attribute is just random at this point. So it's not really serving much of a purpose. Now, you know what? Another thing we needed to do, I needed to test the emptiness, right? Did we have any empty? I don't even think we had any empty tiles on the last one just because there were so many bombs. So I want to generate a lot fewer number of bombs than we had already. So what I'm going to do, I'm going to set this to 10. I'm going to make it a 1 in 10 chance, effectively. And then I'm going to set hidden to be false always right now. Nice. OK, cool. So that's a little bit more appropriate, I think. And now they're all being shown on the screen. This is cool. I like this. This is fun. This is exciting. Yeah, this is super cool. So now let's set it to be true by default, right? So now they're all hidden. Now, this is where we need to start actually implementing, being able to click on the individual boxes, right? So yeah. We can do this. So what I'm going to do is, this is going to be my update function. So in my update function, first of all, I'm going to add this to main. I'm going to say, in main. So right now I'm drawing the grid here. I also want to update it. So game grid update delta time. Again, delta time is the number of seconds that have passed since the last frame. So it lets you keep things normalized across frame rates, which is cool. I'm going to go into here. I'm going to say, local xy is equal to love.mouse.getposition. And now what I need to do is I basically need to iterate over every single grid tile and say, is the cursor within the bounds of that tile, right? And if it is, I need to draw a rectangle on top of that tile. And the easiest way is to take the offsets and to consideration and then just kind of do it based on-- a couple of ways we could do it. So first of all, you need to convert. When you're using the push library, in order to actually get the actual pixels, the proper pixels, you need to do push. And what is this? To world? And then now we can do love.graphics.print x two string x. Y two string y. And I'm going to put that zero and then virtual height minus 16. To world. Pushed to world. Oh, is it to game? Might be to game. There we go. But it's not rendering because I think it's a little bit too low. Let's try it minus 48, let's try that out. No, that's not working. OK, that's fine. Love.graphics.print x to string x, y to string y at zero virtual height minus 48. Oh, that's my bad. That needs to be within the push start, push finish. So that's fine. We can do this. This is a little bit weird. But now you can see it works. We have a virtual resolution, so 384 by 216, again, is our resolution. So we can see that it is rendering. And now the thing is, it's going to need to be I think cast to an integer. I mean, we could do it with floats, too. That's fine. Doesn't really matter. But that's pretty cool. It's going to be important. Let me make sure that I'm meeting everything. If y is less than y height and x, it will enter twice, right? In the state we should be up. No. It won't do the logic twice, if that's what you mean. Oh, levels. I like Bhavik's idea. Level beginner 10 bombs, intermediate 40 bombs, expert 99 bombs. That's cool. Use the check neighbor function, same logic. Abstract check neighbor and use it for a bomb or empty. Oh, I see what you mean. Yeah, yeah, yeah. We could've done that, too. Yeah, that would've been better design. You'd check neighbor, which basically takes an x and y and then does this. The only thing is that what you check and what conditions you check are inverted. And so you would need to pass in negative numbers to get it to work. It's a little bit strange. You could get it to work that way, though, for sure. For checking a [INAUDIBLE],, blah, blah, blah, blah. As an exercise, try and implement it on your own. And actually, to complement that, I'm going to add this to GitHub before I forget to do so. I forgot to add the typing game code, the last bit we did to the stream. And Brenda kindly informed me afterwards. So hopefully I don't neglect doing so again today at the end of the stream. We're going to call this Minesweeper stream, Twitch stream on Minesweeper. Make it public, create repo. Cool. Copy that. Go over here. Exit Python, clear. Where am I? Add it there. Need to go into Minesweeper. Get init, get adstock, get ad first commit. Get remote at origin. [INAUDIBLE] Get remote at origin. That get push upstream origin master. Cool. So that's up and running now. So if you refresh that, boom. Now you have Minesweeper. You can follow along if you want to or add your own logic to it. Currently, the state of it is as such. The next step we want to do is be able to determine whether we're highlighting one of these tiles. So let's do that. So what I want to do is, I kind of want a hover effect. So when I'm hovering over any of these tiles, I kind of want it to show a semi-transparent rectangle. And so I think that's what I'm going to do. I'm going to say self.highlightedgrid equals-- rather, highlighted tile, I'm going to set that to-- actually, no. I'm going to say self.ishighlighting to false because I don't want it to be highlighting if I'm not, for example, over the grid. Like if I'm over here, I don't want any of the tiles to be highlighted just as a user experience. I don't want them to be like, why is there a leftover highlighted rectangle on the grid right here, you know? It doesn't really make sense. So instead, I'm going to-- what I want to do is, I want to-- I have to get a reference to each grid tile and check to see within that grid tile if my cursor falls within it, if it's within the bounds of that rectangle, right? So I want to basically do a four y is equal to-- well, I just don't want to do four y is equal to one. I want to do x pause, y pause. So for y is equal to one self.height, and again, I have to also do this. I have to do x pause, y pause. And then x pause, y pause. That. I'm going to do a double loop over my grid. And then I'm going to say four y is equal to one self.height do four x is equal to one self.width do. And then this is going to let me go over every single tile in the grid, right? And so what I'm going to do then is say, local tile is equal to self.grid at yx. So that's a reference to that particular tile in the grid. I'm going to do if x pause is greater than tile-- they maintain an xy as a reference to their-- oh, they don't even maintain an xy. OK. So they maintain an xy relative to their iteration. OK, so it would be greater than-- so this would be the offset of the grid, then, the first part. So self.leftoffset. Self.leftoffset plus x minus one times tile size, right? And x pause is less than self.leftoffset plus x minus one times tile size plus tile size. And, we'll do a then first. If y pause is-- just, I'd probably ideally combine this into one Boolean expression, but because it's such a long line, I'm going to actually split it into two checks, one nested within the other one. And this is also form of short circuit logic but bulky. Basically lets me avoid checking y pause if x pauses isn't even worth considering. But we're doing kind of the same thing here. I'm basically saying greater than-- and this is vertical offset but it's top offset. Top offset plus y minus one times tile size. And y pause is less than top offset plus y minus one times tile size plus tile size. So in this case, the x of the mouse is within the rectangular border of whatever tile we're looking at, right? So then I can say, self.ishighlighting is equal to true. And then another thing I have to do to make sure that we're not highlighting a rectangle indefinitely after we've gotten outside the bounds of the grid, I should say local highlighting something is equal to false, right? And then at the end of all of this, rather this should be-- I basically want to check to see whether at the end of this whole block of code, I want to say, did we find something to highlight? And if we didn't, then self.highlighting should be toggled back off to false. So it's basically going to match this variable up here that we're keeping track of. We're assuming false but anytime that we get a match on a tile, then we're going to set that to true, right? And then what I can do is I can say, if highlighting something, and this isn't a self variable, then self.ishighlighting is going to be equal to false, right? So basically using this, we're keeping track of over this whole iteration, did we find something to highlight? If we didn't, if we're outside the bounds of the grid, let's make sure that we turn off highlighting. So self.ishighlighting should be set to false in that case, right? Which is right here. Highlighting something is equal to true, something is highlighting is equal to true. And then if we are highlighting something, what we want to do is we want to say, self.highlightingtile and we'll just say x is equal to x and y is equal to y. And what this will allow us to do, now down here in the rendering thing, we can say if self.highlightingtile, then, so if we're highlighting something, if we've flagged something as being highlighted, wait, rather is highlighting, sorry. If self.ishighlighting, then what we're going to do is we're going to say love.graphics-- no, we're not going to do it in here. That's silly. We do outside the loop. We're going to say if self.ishighlighting, then we're going to say love.graphics.rectangle, and this is going to be a white, semi-transparent thing. So we need to do love.graphics.setcolor21110.1 maybe. So what this is doing is, you have to set that global variable for the color. It's sort of like the marker. And we need to do that before we actually draw the rectangle. Because by default, it's just going to be white. So it's just going to draw a white rectangle. We don't want that. We do want to also revert the color after that to love.graphics.setcolor1111. So love.graphics.rectangle and this is going to be at self.highlightingtile.x. Self.highlightingtile.y, tile size, tile size. And this should also be a fill rect, not a line rect. Now, if I'm not mistaken this should work. Maybe not. OK. Let me make sure that this is working. OK. Self.ishighlighting is equal to true. X is equal to x, y is equal to y. Oh, and then I think in addition to that, we also have to do self.highlightingtile.x plus-- or rather we should do-- we have to also take the offsets into consideration, which I forgot to do. So this would be top-- rather, this would be self dot the width offset, whatever that is. And I apologize if I'm going a little bit fast. We're very close to the end. And we probably won't have time to get to the actual solving the game, unfortunately, or the flags or the game states. But I want to get us at least into highlighting the rectangles before we end the stream. So I'm going a little bit fast. I apologize for that. But we will get this part finished for sure. .leftoffset plus that. And then top offset plus that. OK. That should work. Maybe not. Is it maybe too transparent? Maybe do like 0.3. OK. So something is awry. I have failed in some fashion. So OK, we're going over the grid. For y it's a self.height, self.width. Getting the tile at grid yx, which is good. OK, that's what we want. We are calling update, right? Yep, game grid update delta time. Cool. We have the x position here, so we know about that. So we're basically saying if the x position is greater than self.leftoffset plus-- yeah, and there's the x and the y. X minus one times tile size. And x pos is less-- oh, wait. Yeah, OK. No, that's fine. OK. X pos is less than-- I think it should be greater than or equal to, actually. Less than or equal to, maybe. Maybe not. Because then you'll have edges that are ambiguous. But I guess that's OK. Greater than or equal to, greater than or equal to, less than or equal to. Blah, blah, blah. So I screwed something up here. We got to figure out what it is. Self.ishighlighting. Right. OK. What we should do, we should debug a little bit. I'm going to do a couple of things. So the first thing I'm going to do is, I'm going to go to dependencies. I'm going to create a smaller version of-- can I do that with the start font? I guess I probably can, right? Start small is going to be love.graphics.newfont. Fontsstart.ttf at size eight. Actually, I think it said eight-pixel font already. So that's good. So back into our game grid, I can go over here. I'm going to do love.graphics.setfont to g fonts, start small. And then I'm going to set this back to just regular start. So let's try that. Yep, cool. So that's good debug text. Looks a little bit better. Now after that, additionally, I'm going to do love.graphics.print. And then we're going to say highlighting tile to string self.ishighlighting. I'm going to see whether that works, right? Does that actually work? Oh, whoops. That needs to be at this x and y. Whoops, no. Don't delete it. OK. Virtual height minus 36. OK, OK. So it's not even working. So highlighting tile is equal to false. OK. And then just for good measure, I'm going to say highlighting x to string self.highlightingtile.x. And then at y to string self.highlightingtile.y. Break this down into a new line. Wait, where did I put that? Self.highlightingtile, self.highlightingtile, oh. I guess self.highlightingtile probably never got instantiated. That's fine. Self.highlightingtile equals x equals zero. Y is equal to zero. And I apologize, I'm not looking at the chart super in depth right now just because I want to get this finished while we're a little low on time. But I will read all of the comments after we finish this. Promise. Minus 24. Cool. OK. Interesting. So we are getting it but highlighting tile is not being set to true. OK. That's probably the issue. OK. Self.highlightingtile. Self.ishighlighting is equal to true. Wait. If not highlighting something, there we go. Now, the rendering isn't working but it's close, right? You can see the rectangle there. And I can actually probably make it a little bit brighter. So let's go ahead and do that. 0.4. So it's a little bit brighter. There we go. That's cool. Now, the rendering code isn't working for the rectangle. So let's figure that out, too. So something left offset plus self dot-- oh, right. Yep. Minus one times the tile size. One times the tile size. Cool. Now as you can see, we have a visual indication of the tile that we're hovering over. So I apologize. That was a little bit of a long amount of time for a somewhat anticlimactic feature. But it's pretty important. It's a nice feedback to at least know, when I click on this tile right here, for example, or this tile, that's going to be the tile that opens up. And makes the game feel a little bit more immersive, right? It's an important detail. That stream altogether took a longer amount of time than-- I mean, I anticipated it might take up to two streams to get this whole game up and running. But I don't know. I don't know. We can definitely get this done by next stream. I would have liked to see it get more finished today. But we have a pretty good foundation. We saw that the bombs are generating. We saw that the numbers are generating. It's great. The next stream will be even better because we'll talk about recursive algorithms. We'll actually get everything solved. Let me make sure that I'm committing everything first. So highlighted tiles, do that. Hit push. We'll say next Monday will be the continuation of this. I apologize we didn't get the whole game finished. It's kind of a larger game. And I always underestimate how much time it takes to interact with the chat. But that's my favorite part of doing this. So I will not compromise on that detail. Let me just go back up to the chat, make sure that I read everything. Ba, ba, ba, ba. About the number of bomb checking, what about a double four looping continuing when you're out of bounds? I'm not entirely sure I understand that implementation, if you want to maybe elaborate on it. Oh. Check out of bounds pause that takes an x and a y? Sure. Yeah, it's something like that. A recursive way to do it? Yeah, you probably don't need recursion for checking just your immediate neighbors, though. But yeah, recursion is how we're going to be doing the actual checking for when you click a button, the actual checking of that, that will be recursive. But the actual instantiation of the game grid, the creation of the bombs, the creation of the indices with the numbers, that doesn't have to be recursive. I guess it could be recursive, but there's no real need for it to be recursive. Oh, shoot. Asley [INAUDIBLE] caught the flu. I'm so sorry to hear that. I know that that can be very brutal. Hopefully you're staying OK. It's an unfortunate timing this week if it is indeed your birthday. So sorry to hear that. Hope you get well soon. Hope you're able to maybe at least watch this later, maybe even on Monday so that you can watch both of them together, the first part and the second part. But thanks for tuning in to at least let us know what's going on. I was very surprised to hear that you weren't joining the stream. So it makes total sense now. Yeah, get well soon. Friday party. Yeah, that's true. Staypeaceful89, hey, everyone. It's been a while. Yeah, it has been awhile. Thanks for joining. It's been a long time. Hope you're able to catch most of this. If not, at least you'll be able to see it on YouTube after the fact. Forsunlight, thank you so much as always. No problem, it's my pleasure. Anytime. We'll do it again tomorrow and the next day. Rodrigo will be joining us tomorrow for a game of 15. And then David will be joining us on Friday for render 50, which is a Python-based source code to PDF application, which is pretty cool. We use a lot for a printing source code and that sort of thing, or rather, I should say for lecture and that sort of thing. Push it, come on, says Bhavik. Well, everything's pushed so you should now be able to see all the changes. CS50 or game development? CS50, didn't know we had the classes. Yeah, it's hard to compete with CS50 proper. But we do have a game development class that I taught, which is cs50.edx.org/games. You go to there, you can go to cs50.edx.org/web and you go to cs50.edx.org/mobile. So games I taught, Brian taught web, and then Jordan Hayashi taught the mobile react native course. All pretty cool courses. Brian's and Jordan's are awesome. Mine is OK. I think I could have done a better job now than I did at the time. But that was my first class, so I think all things considered, I'm pretty happy with how the curriculum turned out. But yeah. You can always do better. JPguy says, thank you for the stream. My pleasure. Bhavik Knight says, great stream. Thanks, Bhavik. I appreciate it. Bellacurious, thank you very much. Thank you, coltonicedream. Much appreciated. Empty tiles unpressed would be recursive. Yes. Empty tiles unpressed would be recursive. That is exactly how we're going to be doing the revealing of tiles in the next stream. It will be a recursive algorithm where the base case is hitting a number tile or a bomb, in which case, the bomb would not be revealed but the number tile would be revealed. NACLEric says, thanks for the stream. These Lua apps inspire me to make an Ascii game. Awesome. Yeah, definitely share it with us if you do. And mkloppenburg says, thanks once again. My pleasure. Appreciate it, Martin. Thanks for joining us. Will Nick start AI on edX? That's a good question. No plans at the moment. But we can maybe talk about that with him. Pudavedatel says, thanks, Colton, very interesting. Thanks for joining and thanks for the follow. Joe, don't be so hard on yourself. You know, I don't see it as being hard on myself. I see it as thinking I could do something a little bit better than I did last time, right? Always sort of being self-reflective and analytical and making better stuff, I see no harm in doing that. Now, saying oh, I'm terrible. I suck. I'm so bad, I think that's negative and that's toxic. You don't want to do that. And that doesn't help anybody. You want to be productive with how you think and how you approach solving your problems. So I think those two are two separate ways of thinking and being hard on yourself, you're right. You don't want to do that. That's bad. But saying, oh, you could've been better, I don't think there's anything wrong with that. I think that's fine. I'm not sure what language that is. Mkloppenburg is speaking to JPguy. Is it Dutch? Since I think JPguy's from the Netherlands, right? That's a good philosophy. I'm only 15 and I want to become a programmer and you inspire me. Oh, awesome. Well, thanks for tuning in. I'm glad that you enjoy the material and the content. And I hope to see more of your stuff. And again, I'm going to pitch it again, so bit.ly/ what was the link again? I don't remember exactly offhand. It was bit.ly-- that's what it was. CS50twitchcodereview. So go to there if you have some source code, if you're a beginner or an intermediate programmer. This is much more for what that is catered towards. But submit your source code to us via a GitHub repo or a gist and we will potentially take a look at it on the stream and tell you what things we like about your style and your design and what things we think might be better. Right, and there's always usually room for improvement. And there's certainly a lot of things that, as a beginner or intermediate, it's good to reinforce good habits as much as it is to point towards bad habits, right? So yeah. That was Minesweeper part 1. So part 2, we'll do the actual recursive finding of bombs, we'll do losing the game, we'll do point totals. All of that stuff will be in the next Minesweeper stream next Monday. So again, tomorrow's Rodrigo, Game of 15 in Python. And then on, Friday David with Render 50, which is another Python app for rendering PDFs. And then Monday, I'll do my Minesweeper part 2. And then I forget, we have a couple of other streams next week. But these will all be posted on the Facebook group, CS50 Facebook group, which is facebook.com/cs50. Definitely follow that page so you can keep up to date with all of our events. Join the CS50 Facebook group, so it's a separate page. The CS50 Facebook group, I always post all of our events. And follow us on Twitter, follow us on YouTube, follow us on Reddit. All that fun stuff, do all of that. It's all good stuff. Lancemagus says, my sauce code is really bad. No way I'm showing. Yeah, don't feel scared to show us. We would be happy to look at it and give you some feedback. And I think especially if you're a beginner to intermediate, I think getting feedback on how things could be better is super vital to help you break habits that might be really hard to unbreak later on. So feel very encouraged and don't be discouraged and don't be afraid of the criticism, because that is ultimately how you can become a better programmer. Spaghetti goes best with sauce. No worries. Cool. Thank you, [INAUDIBLE]. Awesome, thank you. Or I should say, you're welcome. Cool. All right, everybody. See you tomorrow with Rodrigo. Until then, enjoy the rest of your evening and take a stab at the source code that we came up with today. See if you can maybe implement your own features, do your own version of the recursive algorithm before we do it next Monday ourselves. That's how you learn, ultimately, much better than you would learn through copying my source code. And solidfury says, peace. Good to see you, solidfury. All right. Everybody, enjoy the rest of your day. Until then, this was 650 on Twitch doing Minesweeper. And I'll see you next time. Bye-bye.
B1 tile rodrigo sanchez grid colton ogden ogden MINESWEEPER FROM SCRATCH (PART 1) - CS50 on Twitch, EP. 27 8 0 林宜悉 posted on 2020/03/28 More Share Save Report Video vocabulary