Subtitles section Play video Print subtitles COLTON OGDEN: Good morning. Good afternoon. Good evening, depending on where you are in the world. My name is Colton Ogden, and this is CS50 on Twitch, where we code stuff from scratch, or where I typically code things from scratch. If you're a first time viewer, that's what we'll be doing today. We're going to be looking at the typing game. So this is kind of an archetype for games. It's not a very common archetype, certainly. But it's a game that you may have seen implemented in such games as Typing of the Dead, where you have a word in front of you, and your goal is to type this word without mistakes. And when you do, you do something like shoot a zombie, or there are certain top down versions where you're a mage and you cast spells. Or you can just do it for sport and see how many points you can accrue over a span of time. And that's what we'll be doing today. So shout outs to everybody that's in the chat already. So we have a bunch of folks. I'm going to go scroll up to the very top. So Whipstreak was here actually from the very beginning. So he says, I'm here. Bhavik Knight says, hey all-- Bhavik Knight, a regular. Whipstreak also very much a regular, but I think Bhavik Knight hasn't missed a single stream, if I'm not mistaken. The Korean Bot says, hello there. The Quran Bot, sorry. I mispronounced that. TazNoor, hi everybody. Hello, TazNoor. Sashikant32, another regular, says hello. Buddha Nag says, this is old student, with a smiley face. And then later on, I believe mentioned here she was doing a final project and looking for suggestions. Bella Kirs, hello to you. Let me just make sure I'm not missing anybody. Asley, Nuwanda3333. So shout outs to Asley. She's responsible for this particular stream and the stream preceding it. So she recommended that we do a typing game based stream. And so that's what we're doing today. So shout outs to her. Everybody can thank Asley for her recommendation. She's also recommended many other streams that we have done so far. And asking Buddha Nag, who asked, you guys can suggest me a final year project, please. And then Asley was saying, no one can actually give you an answer on your final project. It needs to be something you're passionate about. And I agree with that. It's definitely something when you're doing a final project or some sort of large project, it helps to do something that is pertinent to your interests, but also intersects with your area of expertise. To Bhavik Knight's point, asking for, what is your expertise area, Buddha? And then Bella says, you can watch the CS50 Fair for inspiration, which is true. Yes, we did showcase many awesome projects for the CS50 Fair, which is on YouTube. Kirayue says hi, CS50. Hello, Kirayue. Hello Shamxr. I suggest having a brainstorm session, be it here or with your friends. Yes, very true. Brainstorm sessions are amazing. I was wondering how to make an application about finding teachers near us, says Buddha. OK, that's an interesting use case, certainly. Whipstreak says, where's my Colton at? JP Guy is waving, says hello. Hello, JP Guy. Good to see you as well. Nice rhythm, Colton. Thank you. I was thinking to make an app that a student will hire a teacher and make an appointment near him or her. Yeah, that'd be pretty cool. An app like that would therefore require you to have some sort of database of teachers, and then maybe a separate log in system for teachers and students so that they could both register and be sort of paired up with one another. But that'd be very interesting. I saw a project very similar to that at the Yale Fair, actually. There was a student that had something similar to that. So good idea. [INAUDIBLE] says, hi, Colton. Glad to see you. Yay. Hey, Colton. Hey, Colton. Everybody is very awesome. Not for engineering. It's for computer applications final year. Oh, I see. OK. Kirayue, Colton, good to see you. Sashikant, Asley. I missed one CDCI. Oh, right. OK. So just one out of 24 or 25 streams, Bhavik has missed only one, the continuous integration with Travis. That's OK. At least you can watch the VOD thereafter. But still, that's like higher than a 90% attendance rate. So awesome. Well, thanks everybody for joining today. So let's sort of get into what we're talking about. I'm going to flip over to my screen. In case you're unfamiliar with what I'm going to be using to make today's game, it's a framework called LOVE and a language called Lua. So the framework is here, love2d.org. It's an awesome 2D graphics programming framework. Definitely grab it if you're not familiar with it. There are other streams where I've talked about how to set it up and the basics of it. And today we're going to be kind of taking a lot of the principles we talked about on Monday. So on Monday of this week-- and you can watch the video on YouTube if you're watching it after the fact-- but we did Hangman, which is mostly textual, looking for text input from the user, drawing simple shapes, and then trying to fill out a word, and therefore looking at the letters of the word to make sure that they match up with letters that we've already typed, and so on and so forth. Today we're taking some of those ideas and kind of making a different game that has more of a time-based mechanic, insofar as you have 60 seconds to type a word or a series of words that are presented to the user. And for every word you complete, you get a point. And then at the end of the 60 seconds, you're basically graded on how many words you successfully complete. So if you did 20, 25, however many words-- it's not quite the same as maybe a typing test on, for example, typing test or whatnot, where you can see a body of text and then write it out, and then be graded on how fast your words per minute would be sort of traditionally typing. This is more like reactively typing, and only being able to see one word at a time. So it's a little bit different, a little more gamey. This principle, this idea, has been used in commercial games, some pretty cool commercial games, like Typing of the Dead, which was a game that they recently added a new version for Windows, I believe, Typing of the Dead Overkill. That's what the more recent incarnation is of the game. But if we look here-- and I'll try to make it nice and large-- this is what Typing of the Dead looked like, the original version that came out circa 2000. And this game basically has-- it's kind of like House of the Dead, which is a shooter game, a arcade game where you had a light gun, and you would shoot zombies as they come into the view of the screen. And it was kind of tangible in that sense, in that you actually had the physical gun and you're shooting at the zombies. But this game doesn't require you to shoot at any zombies. This game is kind of like, there are zombies coming at you, and you are figuratively shooting at them by just typing words reactively as they come onto the screen. And so for every word that comes on that you successfully type-- in this case, ping-pong and bang-bang-- the game will shoot the zombie for you, and treat it as if you had successfully shot it. And if you miss a letter, essentially the zombie will get closer to you and attack you, and eventually you will lose. Now, of course, to make a game like this, there's a lot of layers. A full 3D game or even a 2D game that has these sort of mechanics, we don't have time to do that in a single stream. Instead, what we're going to do is just illustrate the simple mechanic of a bit of text comes up in a screen, a word comes up on the screen, and you have to type it out. And it will show the full word and your progress. And if you type it successfully in the right order, the letters will fully appear. Eventually you'll get the word. You'll get a point. If you miss a letter, then you'll go back to the beginning of the word, and you'll act as if you failed that. And that's effectively how you not lose, but do more poorly, is that you just don't get as many words typed onto the screen, right? So that's today's game. That's the gist of-- at least the mechanic that we're going to illustrate today in a couple of hours, a few hours. The first thing I'm going to do, as always, is I'm going to go to my Streams folder, where I have all of the repos that I've done so far. And I'm just going to create a folder called Typing Game. I'm going to click and drag this to my text editor of choice, which is Visual Studio Code. If anybody isn't familiar, it's an awesome text editor. It's free. Definitely grab it. And it's an empty folder. So I'm just going to create a new file called main.lua. The language that we're going to be using today is Lua. If you're unfamiliar with Lua and LOVE, definitely check out, for example, the Hangman stream, which we did on Monday. I feel like we did a pretty good job of covering a lot of the basics of LOVE 2D. But certainly, if you go back farther onto some of the Twitch VODs on YouTube or on Twitch-- more so on YouTube, because the Twitch VODs have an expiration day-- if you go back to some of those older videos, you'll see that we cover LOVE 2D the framework in a little more granular detail, and cover things like a game loop and like the love.load, love.update, love.draw, in a bit more detail and a little bit slower. But today we're going to kind of go a little faster. But we'll still cover more or less the basics. And I'll give you the rundown of what's going on. Another important thing that I do want to do-- and this is sort of what I try to do most of the time when we do streams-- is I'm going to go to GitHub. And it's a little bit slow. For some reason, GitHub always takes a minute to load up. GitHub is an awesome website, if you're unfamiliar, which allows you to store source code and allows you to store the history of source code and collaborate with other people and do a bunch of other awesome things, automate deployments of your applications. I'm going to create a repo called Typing Game Stream. Typing game implemented on Twitch. I'm going to make this public. This is so that I can push all my code at the end of today. And if you want, you can take the source code, mess with it, do your own stuff to it, and get some more value out of it if you decide not to code along with me, which might be-- maybe if you're busy at this exact moment in time, you don't want to do that. That's fine. Or if you want to get the code right away and implement it, yeah, absolutely. So I'm firing up my terminal. I'm going to go into the folder that I just created, Typing Game. I'm going to get init. I'm going to add all my files. I'm going to say initial commits. And if you're unfamiliar with what I'm doing here, definitely check out Brian Yu's GitHub seminar on the CS50 YouTube, or his first lecture in the web course that he taught. Did an awesome job showcasing how to use the basics of Git. I'm going to get remote add origin. This is just letting me be able to push it to GitHub and do all that fun stuff. And again, if you're not super familiar with GitHub, they actually have all the steps here, sort of the same thing that I'm doing here. Every time you create a new repo to give you the command line instructions to actually get your code onto GitHub. So I've done that. Now I have a get repo. I'm going to clear this because it's a little bit too much. I have a get repo. I've pushed it. There should be a main.lua now on there, which is empty. I haven't done anything in the main.lua, but at the end of the stream, I'll push it with all the changes that we've made today. And you can follow along or download the code and make your own modifications. Awesome. Sandwhich says, this reminds me of Typing of the Dead. And that's exactly what this game actually was. Or, well, I don't have the image up anymore. But I googled image Typing of the Dead, and that is what the game was. This game, all of these games-- this was the screenshot in particular-- it was indeed Typing of the Dead. And that's the mechanic. That's really to illustrate the mechanic more so than the implementation of Typing of the Dead. Because the core game loop just involves you reactively typing words as they pop up onto the screen. So that is the Typing of the Dead. House of the Dead is the game that Typing of the Dead was inspired by. They use the same assets, and actually I think that it was literally the same game, just with typing mechanics. But yeah, it was a cool way to integrate typing, which isn't always all that exciting, with some sort of gamification. Fun stuff. Cool. Let me just make sure I didn't miss anything. JP Guy is plugging Brian's video. FTC227, thank you very much for joining today. Cool, cool. All right, so let's get started actually doing some programming here. So Typing Game. So this is the obligatory header comment, comment block. Name, email, title, nothing all that terribly useful. But this is how you do comments in Lua comment blocks. You can also just do a single comment with dash dash. I'm going to write the core LOVE functions. So LOVE expects a certain set of functions to exist at the beginning of your-- sorry, in your main.lua. And these functions it will call automatically as part of a game loop that it runs underneath the hood. So it calls love.load at the beginning of the game. It calls love.update every frame, with DT passed in, which is the number of seconds as a fractional number since the last frame has passed. And love.draw, which is just meant to be specified for all the drawing operations per frame. So update gets called, then draw, every single frame. So usually every 1/60 of a second, every 0.013 milliseconds-- or sorry, 0.013 seconds. And then there are some other functions, like-- and I think we will need this-- love.keypressed key. And I'm not going to take the Hangman code that we did last stream and copy and paste that. We're going to do everything from scratch. But I might refer to that if, for example, I need to remember something that we did last time. It's all still fairly fresh, so I might be able to do this just from scratch without doing that. But we have that option. It's in my folder if needed. And definitely, if you have any questions or suggestions along the way, as always, definitely plug them in the chat. I try to do my best to keep up and read every single message. And if I miss your message, definitely let me know, and I will read it. Cool. So the game altogether is going to look-- and I feel like I owe it to sort of draw this out in advance, just so that we know what we're aiming towards. But I think the ultimate goal-- and actually, let me get my tablet here, just cause I can do a better job of illustrating this with an actual tablet than with my finger on a trackpad. But I have a little Wacom tablet here. So super nice. I'm going to get rid of this keyboard here. Get a bunch of stuff. Got a awesome writer's room with a bunch of cables and awesome cool stuff. But I did a poor job of setting up. So the table's a mess. So I'm going to clean this. But now I have a tablet, or at least I should. There we go. So now I can actually draw if I want to. And this is an awesome tool. Dan Coffey, one of CS50's producers, head producer, he actually created this application, draw.cs50.io, and we had a stream on it. So if you're interested in how it works, definitely check it out. So this is what the game is going to look like. I'm just going to write game view. And obviously, there's not going to be the word game view on the screen. But we are going to have a word kind of in the middle, just like this. And then below it is going to not-- I don't think be like individual spaces for the word, but more like-- it's just going to be empty, and then as we type, we'll have W, and then it'll type O, and then R, as we typed R. And then if we fail, it'll go back. All of these will sort of clear away and go back to an empty string below it. And then up at the very top left, I think we should have some kind of timer. So maybe 60, and then it'll go down to 59, blah, blah, blah. It'll tick down up in here at the very top left. And then at the very top right, I think we'll have our score. And so this will just be kind of 1 and then 2 and then 3. And then-- actually, you know what? I think it might be good for us to make the score relative to the length of the word. So we can make the score increment. For every character of the word, we'll add one point to the score. So if we-- so a word in this case will be worth four points when we complete it. Because that would be cool. It kind of balances out somebody who-- like for example, if you and I played the game, and maybe I was unlucky and I got like 10 really long words and you got like six really short words, you would be able to type those six really short words probably faster than I could type the really big words. So it's fair that we get higher scores depending on the length of the word. So we'll do that. And that's a design sort of consideration for us. And really, I think that's ultimately all we need for the game view. I mean, just a timer, the score, and then the word. And then below it, the version of the word that we are ourselves typing. And we can maybe do them in different fonts, so we-- or sorry, not different fonts, but different colors. So word could be, for example, in maybe in yellow. And then this, the actual word that we're typing, could be in white. Just to kind of help us illustrate the difference between what we're typing versus what the word we're aiming for is. And I think this is how Typing of the Dead works too, based on the screenshot that we just looked at. But yeah. Bhavik Knight says, how can we use a tablet to draw on draw50? You should just be able to get a tablet and plug it in, and then you might need to install drivers, depending on how old the tablet is and what your operating system is. But usually if you're using a Wacom tablet, for example, Wacom releases all their drivers on their website. So you just choose whichever your tablet is and your operating system, and then just install it. And it just works. It just magically works. But yeah. Cookie Bow, hello, good to see you. So this is going to be the game view. I don't know if we're going to do a lot of game state today and worry about like a start and an end. I might have like a thing that says press space to start, just so that the game doesn't immediately start and start ticking down in time. I think the user should be able to, at their own pace, decide when they want to start the game. And then when it ends, maybe it'll display their score. Excuse me. So I guess there will be a state. There'll be like a start, a game, and a score state. And that's effectively all we need to worry about. And we probably don't need to use state machines for that, because those are going to be very simple states that don't have much logic beyond just pressing a key for the start and the end. So yeah, that'll be I think the gist of the game overall. That'll be the meat and potatoes of it, so to speak. So we can get into it. Again, I love draw.cs50.io. Great tool. Awesome. I think I should start more proactively preceding this stream with a view of what the game will look like, just so we know what we're working toward. Awaro, thank you very much for following. All right. So I guess what we can do is we can sort of sketch out what everything will look like, just so we can see it visually before we start actually plugging in or wiring the application. So let's do that. So I'm going to say, I want my window width to be 1280, again, just like last time, and window height to be 720. And then love.load, I'm going to do love.window.set mode. And this takes in the width and height to create the window for LOVE. This is a LOVE 2D function, love.window.set mode. And if I run this, now we see that the window takes up-- this is a LOVE window. It's taking up the entire screen. I'm running my laptop right now in 720p mode, just so everything is nice and visible. I'm going to make it so that Escape will exit out the window. So I'm going to say if key is equal to Escape, then love.event.quit. And we've seen this before a bunch of times on stream. Love.keypressed just waits for any key to be pressed, and you can define the behavior for each key inside of that function, and LOVE will do this code whenever you press that key. All you have to do is just check to see what the key is, and then execute logic accordingly. You can do whatever you want in this function, but this is typically what you'll see. You'll see if something, so the key is equal to some value, do some behavior. So in this case, we're calling love.event.quit. It's just a function that LOVE comes with that lets you quit the program. So now I can press Escape. I don't have to Command Q or click the red x at the top left, and the game indeed quits. Super simple. The game probably isn't going to look that beautiful today, just black and white probably with yellow text as well. So I'm just going to start drawing some text. I will, I think, pick a nicer font than what we used last time, maybe a monospaced font, just because last time the font was a little weird. We had the L and the M were kind of weirdly spaced. It was a nice enough looking font, and I think for most text purposes, it works just fine. But let's do a pixel or bitmap font that's monospace, just so everything looks kind of consistent. I'm using dafont.com. This is a website I love to use to find fonts. They all have their licenses shown on the right side of the page, as you're looking through them. So I'm probably going to choose one that's 100% free. Upheaval's OK, because it's all caps. And we don't really need to differentiate between upper and lower case for this example. But Pixellari's not bad. It's not monospace, but it looks OK. Press Start to Play. This is one that I use a lot. It's a very NES looking font, if you're familiar with the old NES and the games. Most of the NES games looked like they all had the same exact font. So I think that's what it's trying to emulate there. Neoletters, this is a cool looking font. I like that. Runescape, which is public domain, just the font that they used for Runescape, which I hear they're sort of bringing back, which is interesting. I remember playing that many years ago, and it was a very grindy, but at the time, pretty cool game. Written in Java too, which is a rare thing to see for a sort of online game like that. Silk Screen's cool. I'm trying not to spend too much more time. Let's just figure out what would be a nice font. This is a font that I often use, 04b03. There was a really cool font that I used when I did a sort of a version of this. I think it might have been this one, Alchemical. I'm going to download this one. This is public domain, so it's free to use for basically whatever you want. So I downloaded it. It gave me a TTF file. I'm going to go ahead and copy that into my streams, typing game. I'm going to create a Fonts folder, and copy it right into there, alchemical.ttf. I'm just going to call it font.ttf for ease of referring to it from the game. So that's how you get a font. Go to dafont.com, download a TTF file. And then what I can do up here is I can say local font is equal to love.graphics.newfont, fonts/font.ttf. In love.load, I'm going to set that font as my actively drawing font. So LOVE won't do anything with a font when you create-- I've just created an object here. I haven't done anything with it. I've just said, I want a font object. But it's not being used to draw anything, because LOVE basically can only have one font active at a given time. So I can change that and say love.graphics.setfont, font, and then now it'll start using it. It will actually start drawing with it. skinzo1998, thank you very much for the follow. So now I can-- first I have to actually draw something to see that I'm changing the font, that it's actually being used as the active font. So I'm going to say love.graphics.print, "Hello, world." Just a classic, timeless programmer test. And it's very, very tiny. So I'm actually going to set the font to be really large. So you can pass a second parameter to a font object and create it however large you want to. I'm going to create this one to be 64 pixels big. And when I do that, I indeed see that the font is rendering appropriately. Hello, world. And it looks kind of almost like a horror game kind of, but not 100%. Also kind of looks like an oldie font, like a medieval style font. And it's kind of cool. It's appropriate for-- if we are very, very roughly theming our game today on Typing of the Dead, seems appropriate. Asley says, that font would be hard for dyslexics, but then again, a dyslexic wouldn't play a typing game. Very good point. Very aptly noted. I think 64 pixels is going to be good for us today. So that'll be our font size for everything. So every message in our whole game, we'll display that in 64 pixels. Let's go ahead and sort of outline everything appropriately. So I'm going to first, in the very center of the screen, I'm going to emulate a word that the user would be asked to type. And again, as we specified earlier, this should be in yellow text instead of white text. So I'm going to say love.graphics.printf, and we'll just say the word is CS50. And I'm going to say that it should start at 0, x. It should be roughly in the center of the screen, so not virtual height, window height divided by 2 minus 32. The minus 32 is to sort of account for the height of the font, which is going to be 64 pixels. I'm going to say that it should fill the width of the window width. And then it should be centered. So this is a function of the printf function. You basically start drawing it at zero. You say that I'm going to format it relative to the window width, like it's going to be formatted within a block that's the size of the window width, starting at zero. And if you center it, that gives it the result of centering between zero and the window width, which gives it right in the middle of the screen. So I do that. Indeed, 650 gets drawn right there in the middle of the screen. So super cool. It's not being drawn in yellow text. So I'm going to call a function called love.graphics.setColor. And I might have to look up what the RGB is for yellow. I think it's-- what would it be? Would it be red and green RGB? Not 100% sure. I'm going to mess around with a few options here. I'm going to say 1, 0, 1, 1. Nope, that's magenta. So that's not it. I'm going to say 0, 1, 1, 1. That's blue. OK. I'm going to say 1, 1, 0, 1. I think this might be it. Yeah. So love.graphics.setColor, basically it accepts-- oh yeah, and people are saying there in the chat. People-- sorry, the love.graphics.setColor accepts four components, so red, green, blue, and yellow, RGB alpha. Sorry, red, green, blue, and alpha, not yellow, if I said yellow. RGBA, so Red, Green, Blue, and Alpha. And basically these are just components between 0 and 1, often between 0 and 255, that specify how much red, how much green, how much blue, and how much transparency or opacity a color should have. And by mixing and matching different values between those components, you get different colors of different transparency levels. So we're mixing red, full red, full green, and no blue gives us a yellow color, as such. Cool? Now here, like below the CS50 in yellow would be where we would draw our progress. And really, it would be drawn-- hm. So we couldn't draw just the word centered, our incomplete word centered below our word. Because ideally what we'd want to do is have it such that the-- I don't have draw.cs50.io anymore laid out. But if we were to draw-- for example, if we were to have, in the middle of our screen, CS50 like this, and somebody were to draw, like to say type the first C, and we centered what they've typed so far, it would draw here. But that's not what we want. We want the C to start drawing here, and then the S to draw here, and then the 5, and then the 0. So what we really need to do is figure out a way to offset the CS50. And one way we could do that is by centering it relative to a smaller amount of space. We can subtract the amount of space that it would take up. We can get to that bridge when it comes to it. But that's a consideration that we'll need to think about as we implement our game. So we can do that. We'll worry about that a little bit later. But below the CS50 in yellow is where we would want to draw the progress of the word we're typing in white. So draw the current-- sorry, draw the current goal word in yellow. And then draw the progress of the word we're typing in white. And we'll get to that pretty soon. We're going to need some sort of data structure to store our progress of the word, which we haven't done yet. Another thing that we need to model is drawing the timer. So we'll say that that's a to do. And we're going to need to draw the timer in the top left so what we can do is I can say I love.graphics.print. And I'm just going to do a print. I'm not going to do a printf. I don't need to do a printf to draw relative to the top left, because it's going to draw literally there. So I'm going to say love.graphics.print. And then let's just assume-- let's just temporarily put in 60, and that's it. As a result of that, as a result of the fact that I'm just drawing literally love.graphics.print without specifying an x and a y, it's going to assume 0, 0. Window width minus the font width, yes. Effectively, that's going to be it, Bhavik Knight. Good point. And what it's actually going to be, is it's going to be font width up till the length of the current word for what we've typed. Not the full word, because if we did that, it would be shifted before the word actually begins, and it wouldn't make sense, because we wouldn't be centering invisible text to begin with. But if we type in C, for example, we're going to want to subtract-- we're going to want to center it relative to window width minus the length of the string C. And that will give us the centering that we're looking for. So very aptly noted. Top left, we can see we have a slight bug here. And this is the result of the fact that after we've set the color to 1, 1, 0, 1, which is yellow, we're not actually setting it back to white. So you can only ever have one color active at once. And by activating it and not reverting it back to white, we basically put our game into a permanent yellow state for drawing stuff. We don't want that. So what I'm going to do, I'm going to say love.graphics.setColor, 1, 1, 1, 1, after I print CS50. And then you can see in the top left, we do indeed get our timer printed in the correct color. Now, the next thing we want to do is draw the score in the top right. And I'm going to say love.graphics.print. And I'm going to do the same thing here, but I'm going to say, I guess, 24. Now, this actually isn't going to be a print. This needs to be a printf. Because what we want to do for this is we want this to actually be right aligned at the top right corner of the screen. So always touching the right edge, but padded depending on how big that number is. And for this, we do need a printf. Because printf, not only can it center, but it can also right align stuff. Left align you don't usually need to format for, but right align you do need to do so. So what we're going to do, is I'm going to say love.graphics.printf. Again, 0. This is going to be 0 again. So x on the 0. So we're going to say, start at the left, far left. And I'm going to say we're going to draw at 0 on the y. We're going to do window width again. We're going to say the formatting needs to be the width of the screen. So it's from 0 to the end of the screen. And then instead of centering it, I'm going to right align it. And this may or may not be easy to see, depending on the text chat. But at the very top right-- I'm going to move the window here-- you can indeed see that we have 24 written out at the very top right, right aligned and not center aligned. And today we actually also have a very special guest. DAVID: Wow, a guest. Guest is here. David has to leave really quickly, since the team is going to see me on the internet in a few seconds and realize that I'm not where I need to be with them. But I just wanted to wave hello to everyone. COLTON OGDEN: I'm going to get some very angry messages here soon saying, why the hell is David over there? DAVID: I see you got some more Lua going on today. COLTON OGDEN: Yeah, we do. We're implementing a Typing of the Dead, the typing game, so to speak. So I'll pull up an image so you can see what we're doing here. Typing of the Dead. DAVID: Colton has claimed for years to be a faster typer than me, but we've never actually competed. COLTON OGDEN: Yeah. So here is what it looks like. So it's kind of like-- in this case, it's an abstraction over a typing game. You're actually fighting zombies. But the goal of the loop is, you type this word here in yellow. DAVID: Ping-pong. COLTON OGDEN: Yeah. Just that word, just ping-pong. DAVID: This looks pretty easy. COLTON OGDEN: So that's ping-pong typing. And then you reactively are sort of looking for these words, and typing them, and when you succeed, you shoot the zombies, and you get a higher score. DAVID: So this is all about making learning fun. COLTON OGDEN: It is. It is. DAVID: Really it's just a typing game with some scary pictures behind it. COLTON OGDEN: It is. And notice that we chose kind of a scary font too, so we're really bumping it up. DAVID: This is great. I'd love to play later on. I have to go to another class actually. But looking forward to tuning in online. COLTON OGDEN: Yeah, we can compare scores on stream later, if you want. DAVID: There we go. Nice. Well, good to see everyone. I'll try to pop into chat later as well. COLTON OGDEN: Oh, notice that we're also not the Simpsons today. We're not turning yellow. DAVID: I know. You look a lot better today. COLTON OGDEN: Thank you. I appreciate it. So do you. So do you. DAVID: Bye, everyone. COLTON OGDEN: Cool. See you. Good luck. All right. So we have the different main textual components on the screen now. So we have CS50, our yellow target word. We have our timer in the top left, and then we have, if I scroll here again, the score place holder in the top right. And this is a common thing you'll see a lot, particularly in web development actually, sort of building up a mock-- so this is just a mock-- of a UI before you actually wire it together, before you implement all the pieces, just so that you make sure everything looks OK. And then we can start adding the logic and the functionality as we need to. So yeah, everybody, thank you so much for giving David some nice shout outs there. Good idea. Next secret stream could be you competing. Yeah, that would be-- I mean, between us, I don't think David stands a chance. But you know, we'll see. We'll see. So yeah, those are all the main UI components, I guess. Again, we're going to get the actual progress of the word we're typing in white later drawn. Well actually, you know what we could do? We could test this out. We could determine whether the logic that Bhavik and I talked about is accurate. So let's do that. Let's say love.graphics.printf. And I'm going to say CS5 for right now. And for consistency, I'm going to change this from double quotes to single quotes. CS5. So let's pretend that the user has typed just the first three letters, but not the zero, the remaining character. I'm going to do kind of what we did up above. Window minus 2. And then I'm going to say plus 16, just so it's a little bit below the other word. Window width and center. And then we can sort of see what I was alluding to before, the fact that the CS5 isn't right below the CS50. It's kind of shifted over because it's actually centered relative to its own length, its own size. It's not mapping right underneath it. Because we're just literally centering across the entire width of the screen, right? So what Bhavik suggested was that we, instead of centering relative to window width, we subtract the size of the string that the user has currently typed, which we can do. We can say window width minus-- and then offhand, I have to remember what exactly the function is. But love font text size. This would be something to Google, if you're not familiar, if you don't remember. I'm going to just decrease the size of the browser here so we can read together. It's going to be in here somewhere. It will most likely be in here somewhere. And we might have to look through the documentation to determine exactly where it is. So I'm going to say-- I'm going to go to love.graphics. I'm going to look up the font. I think it's a function on the font class. So I'm going to go here, font, get-- let's see, getWidth. OK. So we can see here there's font getWidth. So if I go to here-- so what it basically expects is that, given a font object, which we have seen earlier, we can pass in a string. And that string will give us-- I mean, it will take the font object. It will get the string that we pass into it, and it'll say-- because it knows the size of its own characters, its own glyphs. It'll give us a number that's actually the length of that particular string. So I can say-- I'm going to bring this down to a new line, just for readability-- I'm going to say window width minus font getWidth the length of CS5. And if I'm not mistaken, this should-- oh, you know what? Actually, that's not what we need to do. We need to-- it's actually the inverse. It's the inverse of the string. We need to give it the-- we need to minus the length of just the missing character. And so that's a little bit trickier. So we need to effectively determine what the string is between what we've typed and the end. So we can definitely do this. I can say-- let's see. What would be the best way to do this? I guess CS50, it would have to be, for example, CS50 sub-- remember, because the sub function is the substring function. So this is what allows us to specify a start and an end index and give us to the end of the word. Asley's saying, does only Lua have the font getWidth function? It's a LOVE 2D function. So it's not Lua itself, but the LOVE framework gives this to you. And a lot of other game frameworks will give you something very similar to this. But what I need to do is I need to determine the width of basically CS50 minus CS5, which is just 0. So I need to get-- I need to take the full string, and then sub it from the length of the string that we've typed until the end of the string. So is there a way to get the length of the string in Lua? So let's determine this. Lua get length of string. It's probably a string getLength function, I would have to imagine. String dot-- I'd imagine there has to be a string.length function. Turn the-- oh, I saw it. Length. Oh, is it just dot leng? String.leng? Oh yeah. Oh, so just call in leng. OK, so that's easy. Oh, string.leng Lua. Oh, but we can also pass it, use the colon thing. OK. So if I say CS50 sub from-- so this would be the-- what's it called? Basically, we need to start it at index 3 until the end. Right? Or would it be 4 until 4? Yeah, because we need to get the character after where we are until the end. So this would be from 4 to 4, not from 3 to 4. Because otherwise that will give us 50. So we need 4 to 4. So this would be the length of the string plus until the end of the string. So let's say-- this is somewhat a little bit complex, but we're going to say CS5 leng plus 1 until CS50 leng. And eventually, this isn't going to be CS50 here and CS5 and CS50. These are going to be variables that change, depending on the current word, and depending on what the user has currently typed. But-- oh, it looks like I missed my parentheses up here a little bit. Let's determine where I-- OK. Sub, do that. Do I have an extra parentheses there? I guess I must. OK, let's try that. Uh, shoot. My apologies. Let's figure this out here, make sure-- oh right, OK. Parentheses goes there. I think? Crap. Oh, no. Printf goes there. OK. So the getWidth then goes like that. OK. Minus font getWidth. It was like that, right? OK. And then center goes there. Where do I have this messed up? OK. I apologize. That goes there. CS50 sub. Is it-- are you not allowed to pass in raw string-- let's test that in Lua. So Lua, and if I say "Hello" leng. Oh, OK. That's what it is, I think. You can't actually call it on raw string literals. So I have to say local string is equal to CS50. We'll call this fullString. And we'll call this halfString, CS5. I'm going to replace this with halfString. This is going to be fullString. This is good. And this is actually better, because this is going to model more closely what we're looking at eventually. It'sTheBatman1 says, hello, Colton. I used to watch your streams on YouTube. I made the Twitch account just for this channel. Great fan. Awesome. Thank you so much. I appreciate you joining in and getting onto Twitch to be a part of the chat. Super awesome. What was here before? Oh yeah, halfString. halfString length plus 1. fullString length. And OK, so if I'm not mistaken, this should work now, right? Yes. Excellent. And just as predicted, it does indeed fit right below the thing there. Oh, I apologize. Let me make the chat a little bit smaller. Let's go here. Mess around with some stuff a little bit. Let me know how that looks. Hopefully it's still readable. OK, cool. Awesome. So we successfully did it. So effectively, what we're doing is we have to take the string that we want to render, which is CS5, the progress of the word that we're currently writing. We need to figure out what the difference is between that string and the full string and shift it left based on that amount effectively, is what we're doing. And so I'm taking the length of the halfString, which is going to be three, adding one, because we want to start the substring-- if you're calculating that difference in strings. I want to go to the next index, so we don't get one extra letter. So starting at the fourth index, in this case, the zero. And then I want to go until the end of the fullString, which also is 4 for CS50. And this will always give us the shifted string. It always shift our progress of our current world over just the right amount. So that is honestly probably the most complicated part of what we're going to be looking at today, is just getting that formatted appropriately and shifted over. But it looks great. It looks awesome. It's working. So yeah. Everything's awesome. And we can test this. Again, we can say the halfString CS, for example. And again, it renders over just perfectly right below the word. And if I were to, for example, make fullString-- I'm actually going to bring these up here. fullString is going to be Colton, and we'll say halfString is Col. And then I'm going to replace this with fullString. Again, that also renders. We can make it arbitrarily long. I can say Supercalifragilistic, and say Supercali right there. And again, everything is completely mapping between the target word and the progress of the word that we're typing on the screen. So everything looks amazing. And I think also eventually this is going to be all caps, just so we don't differentiate between the two. So the user can just type as they want to, and everything makes sense. But yeah, that looks great. That's the UI. That's the gist of the UI. It's not going to be much more complicated than that. So let's get to actually wiring this together. Let's start with a timer I think, because that's a really easy thing to plug off and a cool thing to show in LOVE. The library that we're going to use for that is called knife, so knife library love. And it's here. So you can look and see-- oh, well rather this is the LOVE 2D forums page. So if you're getting into the LOVE 2D programming scene at all, and you want to ask a question of the community, go to love2d.org/forums. They have an awesome set of forums. And the knife library-- again, very appropriate for a zombie-themed stream, because at the end of the world, zombies typically shooting them, stabbing them. The knife library is great. It's got a bunch of really cool modules that let you do some really cool stuff. And we won't get into too much of it, but one of the ones that we will look at-- and I think we've looked at before on stream-- is called knife. And there's a GitHub link here. So you go to that, airstruck/knife. You can clone it or download it from here. Again there's the knife. So just look for this knife image. You'll know that you're at the right library. And in particular, we're looking for knife.timer, which is here at the bottom. And I think we used knife.timer for the snake stream. I don't 100% remember. It's been a little while. I should already have it, actually. So I'm going to go to snake. Snake backup. Am I misremembering? I might be misremembering. I thought I had the-- oh, you know what? We implemented our own version of a timer for that, now that I think about it. So yeah, we haven't really ever looked at timer, I don't-- have we not looked at timer? I could have sworn we did a stream on it at some point. Anyway. I have it here somewhere, but I can't find it. So I'm going to knife airstruck. I'm just going to clone it again. So grab the Git URL there, HTTPS. I'm going to go to-- I'm going to click that, exit out of this LOVE interpreter. See where I am. OK. I'm going to mkdir lib. I'm going to cd lib. I'm going to get clone that URL. So I get the knife library. And now if I go over to my Typing Game folder, go to lib, it's indeed inside of my lib folder. Now what I want is this knife folder. So I'm going to copy that. Call this cloned knife. I'm going to paste this in here, and then get rid of cloned knife. Because I effectively just wanted this sub folder within the repo, which just has all the Lua modules. And now what I can do is I can say, in my main.lua, timer is equal to require lib/knife.timer. You can go into directories. You can sort of go down deeper and deeper by just using dot. Just like you would for a file extension, I can say require lib/knife.timer. And actually, I think it's a little bit different. I think it's not so much the directory, because you do see the folders above is slash. I think it's specifically any Lua module can be referenced from within a folder with dot. So I'm not 100% on that. But typically you'll see path and then the folder and then dot whatever Lua module you want. So knife.timer in this case, which means knife/timer.lua, which is right here. And what I can do now is I can say, in my love.load-- I'm going to just start this from the very beginning-- timer.every one second, I'm going to create an anonymous function. And this is how you basically say every one second, I want to execute some body of code, but I don't want to declare it somewhere. You can define what's called an anonymous function, which is just a function that lives somewhere. It's some code, but doesn't have a name. So you can't call it explicitly from somewhere else. You can only basically put it into a function that expects it, that it can call it later. So that's a callback function. And I'm going to say, every one second, execute the following function, where I'm going to say the current time equals current time minus 1. I'm going to up here say local current time is equal to 60. And then down where we rendered the timer, I'm going to instead print toString current time. And if everything goes well-- OK, that's nil, so that's not appropriate. So current time, because I had a typo. Now we can see-- oh, and that's not going to work. So you'll notice that it's not actually decrementing the 60 at all. So it's still at 60. If you're going to introduce sound effects, can you move it to dependencies, says Bhavik. We're not going to introduce sound effects. So dependencies are going to be unnecessary for this particular application. But if we were making a larger game, certainly that would be something you'd want to think about. I'm going to-- an update. One important step with timer, with the timer library, is that-- and Asley has the right intuition. She says because we didn't call timer, you have to call timer.update delta time within the love.update function, and that will update the timer so that it actually keeps track of when we need to execute those callback functions that we specified, every one second. So indeed, at the top left, you'll notice that it's decrementing the time. So 55, 54, 53. And we can therefore have the logic that says, when we reach 0 seconds, the game should transition from some sort of play state to a score state, a game over, that tells us what score we got, and then expects us to press Spacebar to resume the game once again. So that's cool. We have that working. Supercalifragilistic, we're going to see that a few times before we actually have a dictionary of words. And speaking of a dictionary of words, I think it would be kind of fun to include that and randomize that as we begin. So I'm going to say local words is equal to an empty table. And I want to fill this up with a collection of words of varying sizes. And it would be cool, I think, to use the CS50 speller list. And I'm going to have to read and remember how we actually load a file. It's not terribly difficult in LOVE. But a lot of the time, when you get into this sort of thing, you get into file system related limitations and needing to put stuff in specific places. PSET4, speller, large. Yes, I don't remember offhand what the exact link is. I think it's on our CDN. So I'm going to go to cdn.cs50.net. 2018, fall, psets, four, speller-- I don't know if one of these matters. Spellers texts, is it-- oh no, dictionaries large. There we go. Let's download the dictionary. So it's a large dictionary. And it should just be a list of new line separated strings. So I'm going to go into wherever I downloaded that. Downloads is here, I think, right? Yep. Copy that. It's 1.4 megabytes. It's actually pretty big. I'm going to go into-- now I don't remember offhand if this needs to be in a specific location, but I'm going to put it into here, into my actual directory. This may need to go into my application support directory on a Mac. And this may need to go into a Windows directory, if you're on a PC. There are specific things that LOVE expects for files like this, but I think I can get away with having it be inside the directory itself, the LOVE directory. I'm going to have to look that up right now, actually. So love load text file. Reading from, displaying, and parsing a text file. love.filesystem.read. This is it. OK. Read the contents of a file. So contents and size is equal to love.filesystem.read, then name and a size. And again, this is a big part of not just LOVE development or game development, but just development is, if you don't remember a function, Google it. Get good at googling it. Type the right words that are relevant to what you're trying to do without being too verbose. Often just the keywords are important. And read through the documentation. So very important thing. AIZOTV, hello, thank you for joining. Appreciate it. And thanks, Bhavik, for tossing in the chat pset4 speller large. Cool. So let's call that. Let's try that out first. I don't remember offhand exactly whether the file needs to be in, like I said, the application support directory. But we're going to figure that out together right now. I'm going to, again, contents, size, and basically copying what it shows in the documentation entry here. Read the contents of a file. It needs to be a set of lines, not just a byte. So contents is going to be a string. And so we're going to need to separate that string on the newline character. And offhand, I don't remember what the function is for that in LOVE. But let's just make sure that this is working. So read large. Let's just say that. love.filesystem.readlarge. And I'm going to-- not print. I guess I can do that down here. I'll just say love.graphics.printf toString size. And is this the number-- that's the number of bytes of the whole file, correct? How many bytes have been read, yes. OK. So we'll do that. And I can reference it because these are two global variables. So contents and size are going to be accessible from anywhere in my application. And we're going to move this up above to proceed to love.load functions. So that it's-- and we're going to make them local, so that they're just module level and not global, properly global, which is just better practice. In the event that we had multiple other files, size wouldn't be accessible everywhere. That's a kind of very generic variable name that you don't want accessible throughout an entire application. love.graphics.printf toString size bytes loaded. I'm going to say this should be 0, 0, and then maybe 64 window width and center. So this message is going to be right in the middle of the screen. And it works. Actually, it doesn't need to be in a specific location. So you can see that is indeed 1.4 megabytes. So we have 1,439,228 bytes loaded from this large file, right? So this is an entire dictionary of words that CS50 has included in its problem set. And we can actually look in Visual Studio Code. We can see that. And we've looked at this on another stream when we implemented speller in Python, we looked at this. But these are all the words in the CS50 dictionary, and it's massive. So I don't know how many lines there are exactly. But we can go down here. About 143,091 lines of text. So pretty sizable. We can safely say that every time we play this game, it'll probably be different, quite different. There will never be a same run of the game, so to speak. But the next thing that we need to do is, we need to split this dictionary up. Because again, if we look at the documentation, we can see that it not only gives us contents, but that contents is a string. It's not a list of strings. It's not something that we can say, get a random index in this table, and then make that the currently active word. No, it's going to have to instead-- we're going to have to split this string up ourselves, right? So I thought there was a file system read lines function. Let me look at that. love.filesystem. And then let's go ahead and look and see. love.filesystem.read. Hm. Dot lines. There we go. OK. OK. So there is a function. So there is an iterator. So this actually makes our life a little bit easier. So let's do that instead, love.filesystem.lines. And we're not going to get size anymore. It's just, again, you can look at the documentation to see that we get an iterator. And an iterator in Lua is what we can use to call the pairs function, right? So we don't have to call the pairs function because the pairs function iterates over an iterator, and that's effectively what love.filesystem.lines does. So we can say basically for line in love.filesystem.lines, which is kind of the same thing as calling pairs table for something in pairs of something. But basically, they call pairs underneath the hood in love.filesystem.lines, which is why it's an iterator and it works. That's why we can call for line. Pairs returns an iterator, and then love.filesystem.lines returns an iterator. So I'm going to say for line in. So first of all, let's get this out of love.load. And local words equals empty table. I'm going to say for line in love.filesystem.lines, do-- right, that's correct- table.insert words. We're going to insert into our words dictionary line. And Bhavik Knight is saying, is an iterator similar to a Python generator? Yes, very similar. And then now, we can do-- instead of saying number of bytes loaded, I'm going to say toString the length of words, words loaded! And if you run this, we indeed see, just as we looked at in our VS Code, our editor, we have 143,091 words now in a table, now loaded somewhere in memory, that we can choose. And to test this, let's go ahead and let's seed our random number generator. So this is an important step. Any time you develop a game, and you want to use randomization, you're going to need to call math.randomseed os.time. And a random seed just basically says to your random number generator, here's a starting value. And this is how you're going to base your pseudo random algorithm on. This is what's going to be the foundation of it, so to speak. And then we're going to seed it with the particular value of os.time, which returns the number in milliseconds, since the UNIX Epoch, which is a really large number that's always different every single time we run our game. And as a result, our random number generator will be different every single time we run our game here. So I'm going to change the word now. So instead of fullString being equal to Supercalifragilistic, fullString is going to be equal to words at index math.random. And then it takes in a number, and that's going to be length of words. So we want to get a random index into our words table. So choose a random number between 1 and the size of the table, which is 143,091. And halfString, we're not going to actually know what halfString is. But we can generate halfString by saying fullString sub 1 to length of fullString minus 1. And-- oh, I did an update, which is not what I want to do. This needs to be done at the start of the game. That's my fault. Let's go ahead and put this over here. And-- whoops. local, fullString, halfString. Where did I mess that up? main.lua:19, attempt to index local fullString. Wait, what? Sorry. Main dot-- OK, attempt to index the local fullString a nil value. It's getting-- oh, wait. It's getting a nil value. Oh, because I did this between words being initialized and not-- or sorry, word being defined and then initialized needs to come afterwards. Sorry. My fault. Stupid silly bug. But we can see that now we're getting a random word, sharpens. And since I basically said give me a random substring that's the word minus 1, the start of the word minus 1, it's giving us everything but the S at the end, so sharpen. We can see it aligns perfectly, and it's a random word. Cool, cool. The time since the game started, right, says Asley. I'm not sure what you're referring to. I apologize. Bhavik Knight says, time since the Epoch. Oh. Oh, right. The time that's being generated. The os.time is the-- so if we look up-- this will be a nice little bit of information-- UNIX epic time. And you can see this here. UNIX time, known as POSIX time or the UNIX Epoch time. Basically, it's the number of milliseconds since Thursday, January 1, 1970, UTC, minus leap seconds, apparently. And this is just a really long number in milliseconds that has happened over the last-- what is that, 49 years? Roughly, a little over 49 years. And this number, just by definition, is going to be different every single time you get it. It can only be a different number because it's only ever increasing. So it's always going to be different. And this is why people use this to seed the random number generator, usually in the context of anything. But this is effectively what os.time is, is what it is. And this is something that you see all over the place. You can use-- I think there's a UNIX command to get it too. But if you ever are curious, and you see this in the future, this is exactly what that is. And it's very useful in games programming. So cool. Before inserting words in the table, says ItsTheBatMan. Yes, yes. Thank you. Any particular reason it's 1970? Thanks, that was very cool information. Yeah, because that was the creation of the UNIX operating system, UNIX Epoch time, I believe. I believe that's the reason. Number of seconds-- oh, maybe not. Maybe that's not the reason that it is chosen. I'm going to look at the history section and I think probably determine what the purpose is. Maybe? I'm not sure. It could have been something arbitrary. I had thought I had heard it was because that was when UNIX was created, but the fact that it's on January 1 kind of makes me feel like they just chose an arbitrary year and a starting point. So that's probably what it is. But yeah. No, it's an interesting bit of information. Yeah, no need to apologize, Asley. I think it's cool. I think certainly other people will find the information useful as well. But yeah. We have a dictionary loaded in memory. We have a random word that we're choosing. And we are properly using a variable to encapsulate not only the word, but also the substring of that word. So everything is kind of falling into place. Now the score shouldn't be 24. It shouldn't be starting at 24. We should have a separate variable to keep track of that. So we'll keep a variable called score, set it to 0. And then down here, where we have the score rendered, we're going to change the 24 to toString score. And then now, indeed, we get 0 being rendered at the top right. In case you can't see it, there it is. So everything is great. Now what we need to do is we need to actually type the word that we're trying to type. The whole gameplay loop, so to speak. So why don't we start figuring that out? This function should probably be in a-- so what we should probably do, is we should say local fullString, local halfString, and bring this into another function. Because we're going to need to call this later, when we end up resetting the word. So I'm going to create a new function at the very bottom called chooseWord. I'm going to get rid of the-- oops. I'm going to get rid of the local bit here. And now fullString and halfString are going to be set to a new value. But we can call this-- we can trigger that whenever we want to, rather than triggering it just one time at the start of the application, having to copy and paste that code somewhere else, we can make a function for it, make it a little bit easier to call. I'm going to put this into a function as well. I'm going to say local words, and then I'm going to-- actually, I can initialize that to an empty table. I'm going to copy this. And I'm going to say, initializeDictionary. And I also need to call chooseWord. And then down here, I'm going to also say initializeDictionary. Copy that for loop that we did earlier. Let's run it, make sure everything works, which it does. So everything's a little bit cleaner. You don't want too much bulk up above where your game actually begins, not too much logic, just because the top of the program should ideally just be what's called a data table. So basically, just a definition of variables. Just to keep everything kind of easy to reference and consistent with other paradigms. Generally, I like to only allocate the top portion of the program for variables and library loading. And library loading should often be, to Bhavik's point earlier, relegated to a separate module in the event that we have a lot of libraries and resources that need to be loaded. But in this case, we're not going to over engineer it too much. We're just going to kind of put everything in the same file for right now, and know that if we were to get more complicated, we have that as a fallback to keep everything cleaner. So cool. Everything is still less than 100 lines of code. We're only at 77 lines. I'm actually going to commit this right now. So git add, git status. Make sure everything is good. Make sure that everything is loaded. OK. Cool. Get commit basic UI foundation and dictionary. I'm going to push everything up to GitHub. So once I do that, and I go back to that repo, and I refresh it, you'll see, once it refreshes, the current setup-- oh, large is not here, actually. We are going to need that. Why is large not in there? Does large-- does get not by default include unsuffixed files? Because that would be interesting. Oh, to Bhavik's point, it will overflow in 2038, UNIX Epoch time. That's interesting, actually. I didn't think about that. But yeah, that's a good point. Hopefully by then, all computers are 64-bit, and not-- yeah, that won't be as much of an issue. But that'll be basically like Y2K all over again. And yeah, as they mentioned, because it's when the time started for UNIX computers, and the time stamp is marked at 0. Damnerel, thanks for joining us. OK, so the dictionary is not being included, I don't think, which is kind of a problem. github include files without suffix. Oh, that's adding them to the get ignore file. But that's not what we want. We want the opposite. This is not relevant to what we were talking about. How to track files with no extensions. Here we go. This is for get lfs track. If anybody knows offhand how to add files without extensions in GitHub, just let me know. I've actually never run into this issue before. I mean, yeah. What I could do is just add .txt to it. So I think that's what I'll do. I'm just going to add .txt. Worry about it later. Add, go back into our code. Just say-- where is it? initializeDictionary, large.txt. And now let's make sure it works first, which it does. Awesome. Go in here. If I could do this now-- and then I say added large.txt, git push. Will this now work? Hopefully. Uh, that didn't work either. Wait, what the hell's going on? OK. Modified. Why are these files not being tracked? Fonts in large. Could have sworn git add added-- oh, wait. Am I in the wrong place? Oh. That's why. I was in the wrong folder. That's my bad. OK. Sorry. User error. User error. Git commit added files. Wow, I feel quite silly. I was in the lib folder trying to add stuff that wasn't in there. So now everything is working. I'm actually going to toucha.gitignore. I'm going to go into here, and in my gitignore, I'm going to do this. OK. Git status. Git commit. Remove ds store. Oh, that didn't work. OK, I think I have to get rm.ds store. Cool. So now we have a gitignore. So we're getting rid of the ds store which is cool. And I think the large thing was probably not even an issue. I think I just thought it was because I was being silly. But we should be good. Damnerel, how long have we been streaming? We've been streaming for about an hour and 27 minutes, if you include the music times. So about an hour and 15 minutes. Adamantine-- hey, Colton, and anybody, can someone explain what game we're working on? I got here late. Sure, absolutely. Let's go ahead and take a look real quick. The-- let me get rid of this. So this is a typing game. So what this is going to do, as you can see on the top left, there's a timer. So it starts at 60, goes down to 0. And the goal is to type any word that you see here in yellow, and it'll be shown in white down below the word. And if you type the letters correctly, the word will keep going. And if you get it wrong, it'll revert back to the beginning, where you aren't typing any letters at all. At the very top right, which you can't really see from the chat, is a score. The score is going to be relative to the number of characters that you typed for each word. So for example, the word sharpens has one, two, three, four, five, six, seven, eight characters. So by typing sharpens correctly, you get eight points. Therefore, it's balanced if you and someone else play and get differing lengths of words. And at the end of the 60 seconds, you will be told what your score is. We're also loading a dictionary at the start of the game, CS50's own speller large dictionary, so that we have a more robust selection of words than what we had last time when we basically hard defined a limited dictionary of words for Hangman. So yeah, that's the gist of what we're doing. Didn't know that, Damnerel. I'm not sure what that's a reference to. That's why I customized my prompt to show the full path before one line above it. Yeah, no. Normally mine does. I think for when I taught the GD50 class, we wanted the prompt to only have the dollar sign. And I never customized it to not do that. So that's why. And I can see why now that that's very valuable to have. So silly user error. My mistake. I goofed. But we're good. We made it. We figured it out. Let us get to the actual input side of the game, the actual game part of it. So what I'm going to do is halfString does not need to be initialized to anything, right? Let's see if that works. Yep, it does. When we first start typing, there is going to be no halfString. It's going to be only an empty string. And once we start typing characters, it will start forming the halfString. And so what we need to do is actually look into our love.keypressed and start kind of doing what we did last time actually. In a loop, we're going to kind of check to see if we've typed in any characters, A through Z, at the keyboard. Yeah, Marek has the PS1 export line there in the chat, which is how you basically change your terminal to reflect, as it says, PWD, which is the Present Working Directory. It has that in there, in the chat. Ddaddio, which is actually my dad, is in the chat. So shout outs to my dad. Says hello boys and girls. Good to have you, dad. Good to have you in the chat. We are making a typing game, in case you haven't been following along. So the goal is at the end of 60 seconds, it will test to see how many of these words you can type. We have 143,000 words loaded in a dictionary, and the currently active word is in yellow. And the next part that we're implementing now is going to be actually showing the half word below that word. So the half word means the word up to-- basically, however many characters we have typed that are within that first word correctly. So for example, if I were to type S-H-A correctly, that would be the current half word. So basically just a substring of the current string that's active. So what we did in Hangman on Monday, was we did a for loop that basically iterated over the entire alphabet. And we basically checked to see in every key, for every letter of the alphabet, did the user press that key on this frame? So for-- and we defined the alphabet to be a string up here as a constant. So I can do that. I can say alphabet is equal to abcdefghijklmnopqrstuvwxyz. So that's a string. That's just literally the alphabet in lowercase. And I can say for. And it was a-- I'm going to have to remember offhand what it was. We're going to get every single letter of the alphabet. So I basically say for 1 until the length of alphabet, so from 1 to 26, I want to do local char is going to be equal to-- char, short for character-- char is going to be equal to alphabet sub, so substring. Get a substring off of the alphabet string. So just means we can choose any arbitrary starting and end index. So for example, 1, 1, so from the first character to the first character, that would be A. From the second character to the second character, that would be B. From the first character to the third character, that would be ABC. So we can kind of do something like that and get every single individual character by just saying-- sorry, for i is equal to 1, we can say i, i. So local character is going to be equal to the substring of i, i on alphabet, so from 1 to 1, from 2 to 2, from 3 to 3, from 4 to 4, because we're doing from 1 to 26 here. And then I can say if key is equal to char, which is just basically a way of us getting to check to see if they've typed in every single letter of the alphabet, so from A to Z, this is where we need to determine whether it's the currently sought after character in the word. So for example, s is going to be the character that the computer is looking for us to type, because that's the next character in the word, up to where we are right now. We haven't typed in any characters at all. So it's saying, OK, your current progress is zero effectively. In order to make progress, you need to type s, in order for us to continue, to get to h, and then to get to a, and then to get to r. So we're basically waiting for s. So we need a way to check to see what the currently active character is. We need basically a way for us to see whether it is indeed s. And I've noticed that it's sharpens every single time because this needs to actually go up here. Seeding a random number generator needs to take place before you call any random functions. And chooseWord calls math.random. So I was making the mistake of seeding the number generator after calling math.random, which defeats the entire purpose of that. So now reprisals, overtask, snapshooter. So now it's actually working appropriately. Everybody is giving my dad a nice shout out. Off topic, where do you find these big text files, like the speller dictionary or the US states info? I mean for that, honestly, I don't know offhand. I would say Google it probably. The speller dictionary I have to imagine David or someone else at TF one year did some googling or some research, or maybe even manually did it, or used an API to dump it. But a lot of the time, you can just say US info data text file in Google or something, or CSV, and then use a script to convert that CSV into a text file. Basically massage the data to do whatever you need to do to get it in the right format. We can make a function to check each key press in a given randomized word. Yes, that's effectively-- well, that's effectively what we're doing, yeah. We've allocated it to the keypress function. GDE1984, snapshooter isn't a word, or better not be. It sounds ridiculous. I don't know. Let's find out. Dictionary, snapshooter. I guess snapshot is a word, and I guess a snapshooter is somebody that takes snapshots. And you can also go to Merriam Webster when in doubt and see whether a word is correct or not. And again, I don't know where this word was taken from, snapshooter. Or sorry, I don't know where the dictionary was taken from. The text file that we have, I don't know whether it was dumped from Merriam Webster or from some other API. Different dictionaries will have different sort of expectations or different guidelines and criteria for what's correct when it comes to words. But I'll say snapshooter. And it looks like it is indeed correct, according to Merriam Webster, which is a fair safe enough source of truth. And as I predicted, which was a complete guess, it is a person who takes snapshots. So if you've learned anything from today's stream, snapshooter is a person that takes snapshots. Very useful information. And a very ridiculous word, to GDE's comment. Bhavik Knight, there are many large password cracking dictionaries as well for brute force. Oh, interesting. Sounds like Bhavik Knight has been up to some hacking. Hopefully white hat hacking. Andre who's in the chat. Hello, Andre. Thanks so much for joining us today. Good to have you. We are pretty far along. We have a typing game sort of in the works here. And the next step is actually getting input to work. So why don't we figure out what the-- like I said earlier, how to ascertain the currently sought after character. So basically, remember, every string is essentially a sequence of characters. And we can index into a string based on a number. So index 1 in this string is going to be r. Index 2 is going to be o. Index 3 is going to be m. So we need to maintain a reference to that index. So local current char index is going to be equal to 1. Right? And this means that this is going to be the character we need to look at to decide whether our key press is the same as that character, right? So if key is equal to that character in this loop-- so if the key that the user has pressed to trigger this callback function is equal to the character that we've grabbed from the alphabet, and then we can say and the char-- or rather, well, I don't want to do and here. I want a couple of things to happen. There's two things we want to happen if they press a letter. If it's correct, we want to basically make halfString one character larger. We want to append a character to halfString so that, for example, if they've typed y here, halfString no longer is empty. It becomes the string y. And then if they type o, it becomes the string yo, and if they type h, it becomes the string yo-h. If they type an incorrect character-- so there's two chains, two flows here. If they type an incorrect character, we want the whole halfString to essentially become nothing, right? We want it to revert back to the beginning, because they've gotten it wrong. Right? And in addition, we want to be able to say, if they have reached the end of the string and gotten it correct-- if they've typed in, for example, all the way to the second s in blastulas here-- again, a word that I don't know. Sounds like a medical word. But if they type the second s, they have completed that word. It should also revert down to-- the halfString should also become nothing, then the word should change to a new word, and the score should be added to-- the size of this word should be added to the score, effectively. So we should get one, two, three, four, five, six, seven, eight, nine points in the event that that is the case. So there's a few things that we want to have happen all within this block here, this love.keypressed area. JMC, does Lua have zero indexing or one? It's one indexing. It's a little bit rough, if you're coming from other languages. Yikes is correct. But you do get used to it. You do get used to it. A source of many bugs for me when I was using MATLAB, says Andre. Yes. Really should be standardized. Those one off errors are so dumb. Yeah, no, I mean, I think a reason is probably because it caters to people that are not technically programming language savvy necessarily at LOVE, at least when it was originally engineered. I have to imagine that's the reason why it's one indexed, and a lot of these tools that are more user friendly are one indexed. Humans generally are not used to thinking from a zero indexed frame of mind. But when you're used to programming, it's certainly the opposite. You are used to thinking from zero index. So they can sort of be a double edged sword, depending on what background you have. But you get used to it. Especially if you program back and forth between languages, you sort of kind of get into the habit of constantly keeping track of what the indexing scheme is that you're using. OK. So if the key is equal to the char, if we have pressed this key of the alphabet, if we have typed the current correct letter, else if we typed the wrong letter, right? So just putting some comments in here to flesh out what I want to do. So if the-- I guess we can use char. So if char is equal to. And so again, we're going to have a current char index-- or char index-- depending on how you want to pronounce it. So basically, whatever-- think of having an invisible pointer that's going across each letter as we successfully type it. The first one is going to be p here. So it's always going to start at one. So it's going to be p, then r, then o, then c, as we type letters correctly. And current char index needs to be incremented as a result of that. We'll see that, if we type a correct letter. But if the char is going to be equal to-- if it's the case that char is equal to word-- and I believe it's word. That's the-- sorry, nope, fullString. fullString sub-- so remember, we need to use sub here-- currentCharIndex, currentCharIndex. So this is how we get the character at a specific number, is you use the sub function-- in this case, currentCharIndex, currentCharIndex, both of those being the same will give us the one letter at that index. So if it's one, it's going to give us the substring from one to one, which is one character. Then just fleshing this syntax out here a little bit. So if we did type the correct letter, first of all, let's go ahead and if current char index is equal to the length of the full string-- so if it's the case that-- this is basically our win condition, right? Or successfully typed full word condition, right? If our current char index is the same length as the string itself, that means we've typed the last character. So we can effectively say currentCharIndex is equal to 1. The word itself, so fullString, is going to be equal to words math.random words. And halfString should be equal to an empty string. And again, remember, we did this over here in chooseWord. We made a function that does the same exact things. So I can just say chooseWord here instead, right? Save me a little bit of typing, make things a little bit cleaner. currentCharIndex is equal to 1. I mean, we can put this in here too. Right? Because this is always going to do this, every time we want to do that. Just put as much of that into the function as you can so that you only have to call the function. You don't have to call a bunch of code, right? You can minimize the length of your program. And that's ultimately what should be one of your big goals, conserving, of course, clarity of meaning. You don't want to make your program so short and so arcane that it's hard to understand. But if you can get rid of as much clutter as possible, definitely a plus. Smaller programs are usually easier to debug. So if the currentCharIndex is equal to the length of the fullString, choose another word. This is going to revert halfString to nothing. It's going to change the current word to a brand new word. And another important thing that we need to do, of course, is add the length of the word to our score, which is super easy. We can just say score equals score plus the length of the fullString, right? Now we're doing this before we choose the word, because if we choose the word, then this is going to be a completely different length. So we add the score, and then we choose a new word. And that's how we effectively get to the next word right away when we've fully typed out the current word. And that's if the currentCharIndex is equal to the fullString, the fullString length, and only in that situation. But if it's not equal to the fullString length, if we're not at the end of the word, then what we need to do is we just need to increment the currentCharIndex. So we say currentCharIndex equals currentCharIndex plus 1. And so what that does is it just shifts our invisible pointer over to the next character, right? And so if our logic is correct, in as far as rendering the fullString and the halfString, then it'll render it appropriately, but just by incrementing the counter. So currently it's not doing that. The halfString is just actually being rendered as is. So what we can do is instead render just the-- or rather, is it the case that the halfString-- there might be a more clean way to be doing it than what we're doing, actually. We might not even need to keep track of the halfString. We can just render the fullString subbing from-- well, first of all, Pipalone, thank you very much for following. We can do something here. We can say local halfString is equal to currentCharIndex is equal to 1 and nothing, or fullString sub from one to currentCharIndex. Right? Minus 1. Yes. And then we can print halfString here. And as a result of that, we actually don't need-- is this correct? Is this right? We don't need to keep track of this halfString here. And I think that's correct. Let's make sure that that's right. Basically, what we're doing, instead of storing a halfString, we're just calculating it before we render it every time. So we're basically saying if the currentCharIndex is one, which means that the current character we're trying to type is the first character of the string, then it should just be an empty string. But otherwise, we should just take a fullString, or take a substring of the fullString, starting at 1 and ending at the currentCharIndex minus 1. Which means that if it's 2, this will give us 1, 1, right? So this should actually work quite well in theory. We'll see if my intuition is correct. I believe it is. If we typed the wrong letter, then here is where all we have to do is just say currentCharIndex is equal to 1. Let's see if this works. heelb, and it goes back to nothing there. OK, cool. So everything seems to be working. I'm going to have my score visible, just so we can see what's going on. Let's try that again. heeltap. Ah, so it does work. So we have 7 up at the top. Our score is 7, because heeltap was seven characters. It changed the word to fortuity, right? Let's try that. F-O-R-T-U-I-T-Y. Awesome. S-C-A-N-D-A-- I'm going to purposefully mess up-- V. OK. Cool. So it went back to nothing. And the timer is going down. So I would say that everything seems to be working pretty well. We're getting the right score. It's going back to zero if we mess up. Everything is aligned perfectly. The current word and the half word, or the fullString and the halfString are both rendering completely lined up. And we're calculating the halfString dynamically. Now, there's a slight bug. So first of all, our timer is now in the negatives. We're going to negative 10, negative 11 seconds. That's not very useful. We don't have a start screen to our game, and we don't have an end screen. And the end screen will also fix the bug with the timer going into the negative numbers. But everything is working just fantastically at the moment. I'm going to commit this code actually. Let me see. Get status. I didn't add anything new, did I? Nope. So words and score and halfString rendering working. OK. So now on GitHub, if you pull the code, everything is working perfectly fine for now. We have a few pieces we need to fix and we need to do. But everything's great. Some people are going to bang their keyboard into the wall if that pnemono 45-letter word shows up. Hey, but 45 points, that's a lot of points. The game is sick, says Adamantinebipartine. Thank you. I appreciate that. It's looking pretty good. And the code is actually looking pretty clean. Trying to add comments a little bit more proactively this time. But yeah. And it's only 105 lines, which is very small for sort of the features that we have so far. And very elegantly written out so far. And I mean, 105 lines with a lot of comments. So it's actually pretty condensed for what it is. Now, let's go ahead and implement kind of like a start and an end state to the game. And that will kind of finish it up. So I guess today-- I always sort of give myself a three hour buffer, but we did everything within less than two hours so far. So it's going to be a shorter stream I think, but that should be all right. For folks that are especially trying to get through the entire game quickly and follow along, that might be a good thing. But certainly I'll stick around for questions, and then we still have these two states to finish up. So we'll do that. So first step, let's do a start date. I'm going to go ahead and say local start is equal to true. So I'm just creating a variable called start is equal to true. And if start-- oh sorry, if not start, then-- whoops. And really, this should be if not start and not game over. I'm going to create another variable called gameOver. This is going to be false. And here in love.draw, I'm going to do what we did yesterday, or rather two days ago, and kind of create like a very simple panel so that we can have a basic press Space to start the game. Oh, and a blinking cursor. That's a good suggestion, actually. I kind of like that. I kind of like that idea. Yeah, I'm going to do that. I'm going to do that. As soon as we do the start thing, we're going to implement a blinking cursor. That's a really cool suggestion. So let's go ahead. I'm going to first of all draw. So this is in the draw function. When the game is just starting, I want to draw a box that says Press Space to start the game. So I'm going to say draw a starting panel. If start, then love.graphics.rectangle. Oh, and another important thing we're going to need to do in a second is make sure timer.update isn't updating during the start and game over. We need to reset the timer as well when we restart the game. So we need to look at that. OK, so love.graphics.rectangle. This is going to be starting at 128, 128. So about 120 pixels from the top left. And it's going to be the size of window width minus 256 and window height minus 256. This is so that it is 128 pixels from all sides. Because we shifted over 128 pixels, we need to take into consideration 128 from the bottom right as well to center it. So that's why we're minusing 256 and not 128 again. So again, xy width and height. This also needs to be a fill rect. And I need to set the color before we do that to some light gray, so 0.5, 0.5, 0.5, 1. And love.graphics.setColor to 1, 1, 1, 1. And that's full RGBA, which is white. love.graphics.printf. Press Space to start. This is going to be relative to zero, virtual height minus-- sorry, not virtual height, window height divided by 2 minus 32. This is going to be window with fill and center. And if I'm not mistaken, that looks pretty good. If I press Start, I'm not checking for space in here. So if start and key is equal to space, then start is equal to false. Cool. That works. Then we get beatless. And then I can keep typing. Messed that up. Objectifying. Cool. So that works great. And then my dad suggested a blinking cursor. So I'd like to do that as well. One other thing I would like to make sure though is that the timer is not updating when it's in start. So if not start and not game over, then timer.update. And now we should see the timer at the very far left. It's not actually actively updating until I press Space. And then now it's going to start updating because we are not in start, and we're not in a game over state. So a blinking cursor. That's a cool idea. Let's go ahead and-- how is that going to work? What's the best way for that to work? So we're going to need-- basically, we can do it as an underscore. We can implement it as an underscore. halfString-- OK. So I have an idea for how I want to do this. I'm going to create a variable called underscore. And you could use a pipe character to do an actual cursor too. I'm going to do it as an underscore that blinks, just to kind of keep things kind of more like you would see in a lot of other typing games. I believe underscore is a little bit more common than I guess cursor. I mean, I guess a pipe is good too. They're both fine. We'll do a pipe character. So we'll say local cursor, and we'll set that to false for now. And then timer.every 0.5. I'm going to create an anonymous function. So this is the timer class that we used earlier, which every one second will decrement our timer. We can use this every five seconds to toggle cursor. So I can say cursor is equal to not cursor. And what that does is, if it's false, cursor becomes true, and if it's true, cursor becomes false. So it just toggles the state of cursor between true and false, between 0 and 1, basically. And then in my-- where I basically generate the halfString, the halfString is going to get added to it the cursor, the pipe character, at the index where we are typing the next letter. So I can say halfString-- so I can say if cursor, then. Space this out a little bit. So add cursor to the halfString text based on cursor state. I can say halfString equals halfString dot dot. Remember, dot dot is what you use to add strings to each other. And then the pipe character. Now hopefully this font has a pipe character. If not, it will be a little bit-- it won't render appropriately. So you might have to use the underscore after all or get a different font. But hopefully this works. Let's try it. That's so cool. Yeah, that works. I'm a big fan. Yep. It's a little bit wacky right there though. Yeah, that's the only problem, is that because the cursor is-- it kind of screws up our padding. It screws up the padding of the string a little bit. So a better way to do this would actually be to treat this as a separate string. An underscore that leads. Oh, I see what you mean. Yeah, that might work. Like that? It still causes issues because it's dynamically figuring out the size of the font, the size of the string. And the underscore is thicker than the i in this case. So rather than it mapping up evenly perfectly, it's padding it too much. And so it's shifting it off. So what we need to do is we would need to treat the underscore as a separate string and draw that after the r, which is totally possible, totally doable. Actually not that hard to do. So what we can do is, I'm just going to take this down here and say if cursor, then love.graphics.print. And then I'm going to print an underscore. And I'm going to print it at-- let's see. Where would this be? How would we figure this out? We would say-- could we get the position of the full word and just use the x-coordinate of that for the x of the half word? Ostensibly. I'm not 100% sure whether that function returns a position or not. So this would be like love.graphics.printf. Yeah, it doesn't return anything. And so what we would need to do, you get the center of the screen, because it's not always centered. How would we do this? This would be very easy if everything were left aligned, but because everything is center aligned, it's a little bit more complicated. Little bit of a brain teaser. love.graphics.print underscore at-- well, we can mess around with it a little bit and see if we might deduce something from just tinkering. So it's going to be a printf. It's not going to be a print, most likely. Yeah, because being centered shifted left. Being center shifted left. This would be love.graphics.printf with the window width. OK. So starting at zero virtual-- not virtual. So used to that. And that's such a bad habit of that. WINDOW_WIDTH divided by 2 plus 16. And this is going to be relevant to the WINDOW_WIDTH minus-- let's put this on another line-- minus-- and then we'll end center-- minus-- would it be fullString minus the-- current character minus 1, the substring of that? So it would be fullString, or the minus of that. Irene is in the chat. Hello, Irene. Good to see you. After every word typed, cursor increments should works. Yeah, no, that's what we're doing. But the problem is that the padding is dynamic, and it's relative to the size of the glyphs of the font. And so if we're, for example, using an underscore below where we're using an i, the i has less padding than the underscore, and therefore the underscore is shifting the half word more than the underscore would be. Or the underscore is shifting it more than the i would be doing so. And for all other characters that are, for example, thinner or thicker than the underscore, it would do the same thing. Bhavik Knight says, full minus the half string length plus one. I think that that is correct. So WINDOW_WIDTH minus the fullString length. Would that be correct? Is that correct? fullString length plus-- no, I think it's-- then we have to take the length of the fullString minus the halfString, which is effectively what you're doing. The plus 1 isn't helpful here. Greenie says LOVE 2D. Yes. Oh, I remember you. Greenie996. OK, so the full width minus the halfString width. And we have the halfString width there actually, right? I think that's the case, right? So WINDOW_WIDTH minus the fullString length plus the halfString length. Am I not mistaken? I guess I am mistaken. If cursor then-- wait, did I screw that up? Wait, what? Oh, WINDOW_HEIGHT divided by 2. Sorry. Oh no. That is not what we want. That is right in the middle. OK. No, it's not quite there yet. But at least it's static. OK. Let's see. Minus the fullString length. Oh, length, not the-- wrong function, wrong function. Font, getLength fullString, font, getLength, halfString. Whoops. getWidth, not length. It's close. It's not quite there yet. It looks like very, very poorly written software at this point. Would an if-then else block work wrapping the whole command on line 101? So Irene, we're not trying to add underscore to the string that we're rendering because that screws up the padding. We're trying to draw the underscore as a separate function altogether so that it doesn't influence the padding of the halfString effectively. So no, we need them to be separate. Would it be easier to just manually position the words at WINDOW_WIDTH divided by 2 minus font length fullString divided by 2? Wouldn't it be easier to just manually position the words at WINDOW_WIDTH divided by 2 minus font length fullString divided by 2? Yeah, that would work. That's a simple way to do it. Why don't we do that? Sounds good, Andre. OK. Position the words at WINDOW_WIDTH divided by 2, font length, font-- it would be getWidth fullString divided by 2. Yeah, we can do that. Let's try that actually. That's pretty cool. And then therefore, we have the-- well, does that fix the cursor issue? I guess it does. Yeah. Yeah, I guess it does. Because we just add halfString to that value. And then that's where we draw the underscore. So yeah. So that would be drawn at WINDOW_WIDTH divided by 2 minus font getWidth fullString divided by 2, see if that works. Oh no. And it can't-- it has to be print, and we have to get rid of center. Right? Oh. And then the last thing after that-- so first of all, that is definitely not correct. That is not behavior that we want, that we're looking for. I would say we messed up pretty bad to get into that kind of position. But the reason it's doing that is because if you call print, just print and not printf, this last parameter is a rotation value. And we're passing it WINDOW_WIDTH, which is a really high rotation value. But no, we want it to actually be this. So awesome. But that looks so cool, says Irene. Yeah. A real challenge would be making it rotate over time, like an expert mode. And then you're challenged to sort of still type the word while keeping track of what it is while it's rotating. Greenie says, Lua, why? Because Lua is a pretty easy language, pretty nice, pretty common in the games industry. But also more importantly, because we're using LOVE 2D, which is this framework. It's a really awesome 2D game framework. And it happens to use Lua as its programming language. But certainly, you can game program in almost any language in the world, whatever suits your fancy. But a lot of the time you're sort of bound to whatever the best tools are. And LOVE 2D is a tool that tends to be pretty nice. OK, we're doing the same thing here, WINDOW_WIDTH divided by 2 minus font getWidth halfString divided by 2. Oh, but it can't be halfString divided by 2 in this case, because it's-- we again have to minus this font getWidth thing here, which is that. halfString x, going to be equal to WINDOW_WIDTH divided by 2 minus-- I think that would actually work. So then I can say halfString, and then this will be halfString x, halfString x, do that, bring all this up here. Does that work? Nope, that does not work. We're having a great time, messing with all kinds of stuff. This is the beauty of small features that are awesome have huge ramifications sometimes. And sometimes they have no ramifications. Sometimes getting a simple feature is really easy and awesome. But there's tremendous value in exploring something like this to illustrate why sometimes bug fixes are not so easy, why it takes a long time to release new features for stuff. Let me see. Oh wait, no. It's always going to draw-- no, it's always going to draw it at the same-- yeah, what am I thinking? OK. No, it just needs to be-- yeah, the same thing as-- it just needs to be drawn at the fullString position, and it'll be the exact same. Yeah. I'm silly. I over thought this. We can say WINDOW_WIDTH divided by 2 minus font getWidth fullString. And then I can get rid of this monstrous line of code. And then now still not correctly rendering. Wait, hold on. Oh. Wait. Divided by 2. Right. There we go. Perfect. So now all I need to do is-- so we have everything working the way it was before. We have the autocatalysis. We have the two strings. They're being centered. But we're doing it manually. So now we have fine grained control over where the positions are. We know what they are definitively. So I can say if cursor-- back to the cursor part-- draw it at the-- and not WINDOW_WIDTH minus all this stuff. Instead, I'm going to draw it at WINDOW_WIDTH divided by 2 minus font getWidth of fullString divided by 2 plus-- and then this is where we're going to add the length of halfString. So I can say font getWidth of halfString. Now, if I'm not mistaken-- whoa, OK. That is not what I was looking for. OK, hold on. Why-- oh, because it's being it's being centered. Right. Print, get rid of center. Not zero. Oh, right. This needs to go here. There we go. We did it. That was fun. But now I would say that that little feature actually added a tremendous amount of UX, user experience value, to our game. Right? So shout outs to my dad and other folks in the stream for that awesome idea. We got it working together. It was a little bit challenging because using the auto centering didn't really give us fine grained control over things we're rendering, and it was a little bit difficult to sort of programmatically figure that out. But thanks to Andre, Andre for his awesome suggestion. We can just explicitly, since we know everything's going to be perfectly center aligned, we really don't need love.graphics.printf and its center functionality, because we can just use the constraints of our window and say WINDOW_WIDTH divided by 2 minus the length of the string divided by 2. And that is effectively the same exact thing. So awesome. You guys are incredible. It was a great time. We don't have a game over and a score screen yet. So let's add that as our last feature. Let me just make sure I didn't also miss anything else in the chat while I was coding that. What about SFML, says Greenie996. SFML's a great library for C++. I've used it myself. But C++ is a little bit tricky, especially for people just getting into it. It's kind of a pain to debug, honestly. Because Lua is effectively wrapping a C++ engine that's open GL based, it's not that much more efficient just to use LOVE 2D for 2D games. SFML has a lot more utility for a lot more fancy stuff, for actual engine and 3D stuff even. But for 2D games, I don't ever see myself wanting to go to SFML over LOVE 2D, to be honest. It really depends on the use case though. There's definitely a case to be made for it. Is Colton emo? No, Colton is not emo. Here to say hello, says ForSunlight. Will watch it on YouTube. Cool. Thanks for joining. Appreciate it. And there's a brain game engine. That's pretty cool. I didn't realize that. Keeping it kid friendly, so I won't say it aloud. The exposition should be the same. We could have done this early, like a rectangle box divided in parts equal to the fullString, and put the char in the box as the user types. Oh yeah, a box would be pretty cool actually. I like that idea. I challenge you, Bhavik, to implement that on your own. You can now just append the cursor. Oh true, we could append the cursor. You're right. You are correct. Is that right? Yes, it is. Because we're just putting it in a definitive location. Adamantine, maybe you could add a trailing space to the cursor. I'm not sure if that would be of any use. Now try that with the pipe, says Buddha. Yeah, we could do it with the pipe too. I know that certain people wanted the underscore. But it's as simple as just replacing this character here, so why not? We'll do it. Arguably it looks better, because now the underscore isn't, for example, wider than the f, which it was before. So pretty cool. It looks like an actual UI for a application or something. It's pretty cool. Not terribly difficult though, right? We made it. Colton, I started the game dev course. Still in the first lecture assignment. It's sick though. Awesome. Thank you so much. Yeah, the Pong lecture, it starts off fairly tame, but by the end of the course, you'll have done quite a bit. The problem sets are purposefully pretty-- I wouldn't say too ambitious, but they definitely give you exposure to larger code bases and doing a lot of really cool stuff. [INAUDIBLE] says we fixed the bug by changing the center alignment to the left alignment, right? This was so hard to follow. Yeah, effectively, that's what we did. Not for the fullString, but for the halfString. We set that to center alignment, effectively. Well, what we did was we basically center aligned it. We were drawing the halfString left aligned using the fullString's center alignment. So we determined what the fullString center alignment is. We divide the window by 2, then we divide the string by 2 and move it to the left by that amount. And then we take the halfString, and we just draw it at that same position, just a little bit lower. And as a result of that, the halfString isn't centered anymore. But it's always relative to the fullString. So it's always immediately below it, and it follows it as we type. So that's effectively what we did. And then we just drew the cursor right at the end of that string. So yeah, all in all, it turned out to be pretty nice. Let's go ahead and draw the game over panel. So if gameOver, then-- I'm just going to copy and paste this, the start stuff, down over here. And I'm also-- first of all, press Space to-- we'll say, let's do this. Game over. And then I'm going to say your score toString score. And then this is going to be at plus 16. And then press Space to restart. This is going to be at plus 48. And then up here, if start-- if gameOver and key is equal to Space, then gameOver is equal to false. And in the timer, since the timer is where one second decreases, that's where it sort of maintains its own counter, its own progress for that, I'm going to say currentTime is currentTime minus 1, and then if currentTime is equal to 0, then gameOver is equal to true. currentTime is equal to 60. Right? So this should work. gameOver is true. Let's make sure this is working right. Start and not gameOver. Oh, and score-- well, score gets reset in here. So score is going to be set to zero. OK. So let's just play and then let the-- inhospitableness. knightliness. We'll let the timer sort of run out. Duotone, undreamed, gobblers, dumpty, composites, mc-- ooh. We have a bug. Certain words have apostrophes. That's unfortunate. mcallister's. OK, well that's great that that happened, because we can maybe add that to our code while we wait for this to time out, because I cannot type an apostrophe or have a check. So I'm just going to go ahead and unfortunately wait for that. We can just add apostrophe to the alphabet though, to be fair. We can add, I think, any character to that, and it'll work. So cool. We have a score of 112. Press Space to restart. Press Space to restart. And then mcallister's. So actually what we need to do is choose word as well right there. So let's go ahead and add an apostrophe to this. I'm going to add a-- well actually, this needs to be in double quotes then. I'm going to add a-- I don't think it's going to ask for a dot. Yeah, and the text is a little bit too high up. So what I'm going to do is I'm going to make this 54 and this 18. And then I'm not sure if there are any other characters besides apostrophe that we should take into consideration. I guess we can look at the large text really quick and see. So I see apostrophe. Might just be apostrophe, honestly. Yeah, we'll assume just apostrophe for right now. Let's do that. Press Space to start. Sub-- and nice. Now it actually kindly gave us an apostrophe word right away. So I had apostrophe s, and it indeed works. So now the alphabet stream, because our code was-- the way that we structured our code, all we had to do is add the apostrophe character to the alphabet string, and it works perfectly. So awesome. Whereafter, Canadianization's. That's a word. This is a great way to be introduced to some weird words too. Fictionisation with an s. Tris, carrara, transfusing, crassitude. That's a word I don't know. How crass something is, or someone is? Nonrelevant, augmenter, clavierist. Shoot. Nazify. Oh, did I screw that up? Oh, I need to lower that padding, not make it go higher up. But cool. So score is 202. Press Space to restart. Timer gets back to 60, and then we're at 0 seconds again. Padding is incorrectly-- I incorrectly did that. So this actually needs to be-- wait, did I not WINDOW_WIDTH divided by 2 plus 54 plus 18. Why was the game over messed up? I don't remember. WINDOW_WIDTH divided by 2 plus 54. That's weird. 64? That might be too much. But we'll try 60. Strange. I'm going to set the timer right away to 3, just so we can test right away. Two, One, boom. OK, a little bit better. Let's make it-- yeah, 64 was good. 64. Three, two, one, boom. OK. Good enough. So this is great. This is fantastic. I'm going to start the score off at 60-- sorry, the timer off at 60. I'll keep the words loaded in there, in case anybody wants to grab the code and try it with their own dictionary. Maybe that's something that you want to do. The alphabet, the way we have it is robust enough so that you could add different types of characters to there if you wanted to. It should be 68 I think. Is your calculation correct? I'm not sure. If we have time, can we add that feature? Ask for the player name, and add the top five high score player name window at the game over? That's going to be a bit long, Bhavik, and we're already at the 2 and 1/2 hour mark. So I think we can maybe save that for another-- like a tutorial for another game. It's going to be a little bit of work. And I haven't planned-- I don't think we have enough time for that. But for another game, I can certainly think about adding that feature, maybe on another stream. And if you want to try doing it, you can certainly do that too. You would need to look at the love dot-- I can at least give you a starting point for that. It's in the love.filesystem thing. So you would need to do love.filesystem dot-- what is it? It is-- you would have to write to a file, basically. And it would choose by default in your-- I think it would be your app data folder on Windows. I don't remember your machine. I think you're on Windows. But it would basically be writing to a file in your app data folder for this game, and you would write to a text file. And then you could do the same thing that we did earlier today with love.filesystem.lines. You can create a new state. And this is an example of probably where I would want to use the state machine and create a new state class, because the high score has a bit of logic in it and a bit more rendering code. And it would be a bit-- our main.lua is arguably large enough to start piecing out at the moment. But love.filesystem.lines would be your best friend here. You would need to calculate whether or not the score that you got on this iteration is higher than the high score of a particular index. So you need to iterate through all your high scores, pick the one that's lower than the score that you currently have, and put yours above that one, and then shift all of the other ones down by one. And then you would need to load this at the start of the game, your high scores from that file from your app data folder. And this file system folder exists in different locations on different platforms. Like I said, on Windows, you look, it's going to be in your app data somewhere. On a Mac, it's in application support, LOVE, and then the name of a folder for whatever game. And then here on Linux, it looks like xdg data home love, or local share love. And different thing on Android as well. But yeah, it's a cool exercise certainly. Try it out on your own. It would be fantastic if you could do it on your own. I think you would gain a lot of value out of trying it out. Are you writing 202 words per minute? No, no, no, no. 202 characters in that case. I write approximately 130 words per minute, but 202 would be absolutely insane. But yeah, that's just the number of characters. We can use a database to support that, says Bhavik. Typically you wouldn't use a database for something like that, Bhavik. That's a bit overkill. Because honestly, all you're doing is storing some text for their name and then their score. Definitely don't want to reach for a database too quickly like that. The only time you really use a database for game is usually for like an MMO RPG, kind of like an online game. In which case, your data is a lot more complicated. For most games, arcade style games, or even RPGs, you can just store it locally as some sort of text file or an encoded file that you parse at runtime. That would be much more commonly seen and embraced, I think, for this sort of use case. And for most gaming use cases. Certainly you could use SQLite, if you wanted to. But only if your data model was really complicated. Starting to love love.lua, says Buddha. Yeah, no, it's an awesome framework. Can we convert the character per minute to words per minute? Yeah, all you would do is just count the number of successful words that they typed instead of counting the characters. But this is not an accurate words per minute test. This is reactive typing. It's not the same thing as typing a body of text, which you're going to get completely different values, completely different readings off of those. Because your brain will be able to read a sentence in advance and fill in and more automatically type then reading just one word as you see it at a time. You'll be much slower doing it this way. So this isn't an accurate typing test per se. This is more of a reactive typing sort of game based on just your reaction time, effectively. Did I take a typing test with Brian? I did not. Looking up stuff in the database in games would be an idea really when you have a massive amount of data, to Andre's point. Yes, yeah, absolutely. Would be cool to watch you. Yeah, no. That's OK. I don't think he and I are going to do a typing test. David and I might do one though, because David threw the gauntlet down. David wants to have a typing test, a typing competition. So I mean, if he wants to throw down, if he wants to maybe put a couple of bucks down, let's throw the glove down. Last I checked, he was quite a bit slower than me. But we'll see if maybe he's gotten faster since then. This was a few years ago. Problem is with a database lookup, no matter how fast it is compared to loading a text file, it's still going to be slower than looking it up in memory. Correct. Correct. The use cases isn't usually good enough to merit-- especially the complexity in setting up a database, and then having to model tables, and making your data therefore very rigid, if you're using like a SQL type database. If you're using JSON or something, like a Mongo database, it's a little bit easier to justify. But even still, there's a lot of overhead and just complexity that you could just get by just literally writing bytes to a file, and then just treating that as some sort of consistent encoding, depending on what you're trying to store, whether it's characters or inventories or locations. Games are much more flexible with how they usually store their data, but also much more efficient in most use cases because you can literally determine in a specific encoding for something and write that out byte by byte and sort of take that to be expected every time that you want to load that data. Games traditionally, back in the old days, in read only memory, were all encoded as very unique byte layouts. Bit layouts, even, depending on what you were storing, just to fit as much information as you could into such very finite amounts of stuff. Cool. Thanks, Fatma, for joining. See you next time. I think this is probably going to be the wrapping up point for this stream. So we did a lot today. It was awesome. We got-- the cursor functionality was super cool. I'm super excited about that. The typing game was fairly easy to implement. So hopefully, if this is your first time looking at LOVE 2D, it gave you a sense of what it's like programming in the LOVE paradigm and making games and whatnot. This week, possibly on Friday, we'll do an HTML stream, if not next week. Next week I'm also in the talks with some other folks to get a couple of other streams up for you. And that way you guys can get a break from me. I know we've had a lot of me, and would have a lot of me this week. But certainly, with the undergrads all still out on vacation, it's a little bit tricky to find other people to do streams besides just me. But you know, we had David last week. So we can maybe get David in here again. I think David's doing a stream next Friday. I'm going to have to reconfirm that. I think we're going to take a look at render50, which is an application that we use to convert source code to PDF documents so that we can print them and see them as PDFs. But we'll see. Everything will be released on the Facebook group. So whenever we do get all the details finalized, you can expect it there. If you're not following the Facebook group, definitely check it out. I believe it's facebook.com/cs50. I'll let this load a little bit slowly so I can just confirm this. Yeah. So facebook.com/cs50. Go there. You can see all the events that we post, which we post an event for every single stream. If you're not following our Twitch channel, definitely follow our Twitch channel. If you're not following or subscribed to our YouTube channel, follow our-- sorry, subscribe to our YouTube channel. Because all these videos get posted to YouTube, and you may actually be watching this video on YouTube after the fact. But we do streams every week, or at least try to. Given the holidays, we weren't streaming as much. But definitely follow us, subscribe to us, all that fun stuff, so that you get notifications and you can keep up with all of our awesome content. Especially once all the undergrads get back, and we cover a lot of cool other interesting topics. Did I listen to Walk Like an Egyptian yet, says Asley. I did not. But in the near future, I anticipate doing so. Your streams are fun. No worries. I want to thank you on the snake stream. Everything seems so super scary, even though I knew a little bit of programming, but now I understand almost everything. That's awesome. Yeah, and you've been here very consistently. And that's really the biggest battle is just staying consistent and doing something and attending, coming here and trying to learn, even when you don't feel like doing it. So props to you for putting in the hard work. You put in the hard work, and you will get results. That is just how this sort of thing works. And this is good for me too. I mean, I have to stay consistent with this. And it's helping me stay sharp and get roughly comfortable programming in front of other people. I remember the first couple of times I did this, I screwed up, and it was a big deal. And now we do this every week, and it's not a problem. So thanks to all of you who are watching regularly and afterwards. You guys make this all possible. So these are great. All the streams I've watched are cool, but yours are the best. Thanks, Irene. I appreciate that very much. Yeah. It's a good time. It's a pleasure programming with all of you and getting all of your assistance as well. You guys are very, very eager to help and always provide awesome assistance when I need it. Like my ask the audience, my lifeline. Whom would I be asking if I have some question about Docker? I tried to do PIP 3 install CLI 50, had some errors. Post in the Facebook group and tag Kareem. He'd be one to ask probably. I actually don't have a terrible amount of familiarity with Docker. But if you post in the Facebook, someone will probably respond. Especially if CLI 50 is broken or something or is not working, definitely bring it to everyone's attention. Appreciate everybody for the kind words. Thank you all so much. And thanks, Bella, for tuning in. Thank you for another amazing stream. Appreciate it. All streams are awesome, says Bhavik. Appreciate that very much. I don't miss the other streams. OK sure, says Bhavik. Yeah, it's great. Is this going to be C or Python says Corn-- am I reading it correctly? CornyGM says, is this going to be C or Python? For which stream are you referring to? Are you referring to render50? Because in that case, next week that's going to be Python. And I am probably going to do an HTML stream on Friday, which is neither. But if you're interested in web dev and you've never done any web dev, definitely tune in for that one, if we're doing that on Friday, maybe next week, but most likely Friday. If you need suggestions about future streams for the near future, a stream on implementing splines would be brilliant. So implementing splines would be tricky, like the actual math, calculating that. Because I'm not a mathematician. But using splines in LOVE 2D to accomplish something, if that's what you mean, that's definitely something possible we could take a look at. Because splines are really cool. You can use them for a lot of cool stuff. But if it comes down to the mathematics of it, I would need to spend a lot of time studying that and implementing a lot of from scratch stuff to do that. But no, super cool suggestion. Minesweeper. Oh. That's really-- I like that idea a lot actually, Bhavik. Thank you for that one. I'll put that one towards the top of the list. Because that's a game I've never implemented, and that could be really cool. I'd have to play it a little bit and get a sense of it. But yeah. I'm half joking because Irene and I are currently being annoyed by them. Oh. Yeah, I mean, they're a little finicky, especially if you're using them programmatically and not through like Photoshop or something, where you have visual control over them. But certainly they can be great. They can also be a headache. Minesweeper would be awesome. Oh, the maths part. Yeah, I do not know the math behind it. Adamantine, Donkey Kong Country. That's a bit more ambitious. But certainly other people have asked for platformers. So we can take a look at a platformer and maybe talk about a basic platformer. The games course, Adamantine, if you keep going into it, we do cover Mario, which a lot of the same concepts apply to Donkey Kong Country. So you'll get a sense of how that works if you continue through the class and make it through the Mario lecture, the Mario lecture and problem set. Super fun. Super fun one. We did a lot of really cool stuff there. Irene is chiming in, splines are annoying. All right. Cool, everybody. Well, thanks again so much for tuning in. I appreciated it, as always. Had a good time. We made another game. And we have many more to make. Tune in on Friday this week, most likely, for HTML. If not, next week, next Monday. And next week for David's stream on render50 and possibly another stream, I'm talking to some other people. I have Rodrigo, who you've seen on stream, is doing a stream on game of 15, but in Lua, which is-- game of 15 is a CS50 visit from prior years. We'll be doing that in LOVE 2D and Lua. And he's got a cool distro that he's shared with me and that he's going to go through with all of you very soon. So possibly next week, if not the week after. And then expect a stream from Kareem in the near future as well. And then after that, Nick will be back in town, and other folks will be back in town, Veronica and Emily, all kinds of people. All kinds of awesome content coming in the near future. All right. Well, this was CS50 on Twitch. This was the typing game. Thanks so much. Catch all of you very soon. Enjoy the rest of your day. And happy programming. Bye bye.
A2 font typing string love timer lua TYPING GAME FROM SCRATCH - CS50 on Twitch, EP. 25 5 0 林宜悉 posted on 2020/04/04 More Share Save Report Video vocabulary