 Oh, hey guys, I didn't see you there. I was just about to play the game of the summer. You know, it's not the new Diablo game. It's not League of Dota. It's not Roblox. It's called Fed Nudge 2023. Basically in this game, you operate, you know, in Persona Christie. That's why you're a green plus and you push the federal agents into the hole over and over again. It's an awesome game. The only problem is sometimes you get softlocked and you accidentally push the agent into the wall and then he's stuck there and you can't get him off. In that case, you have to push the arky and get a new level. But basically, this game is just incredible and I'm so proud. It took me two days to code this up. So yeah, and it was done, you know, without any libraries. I think the entire code is like, yeah, it's five, five kilobytes, it's actually less when you assemble it all into binary. So yeah, it's not too big. And yeah, we'll talk about how that works in this video because today's topic is, let me get my window up, is basically reading and parsing files as well as processing key presses, basically just getting input from the user. How would you do that? And as far as I can tell, there's four different forms of user input. You have, we just talked about key presses, that's like what you'd have for games or for navigating. If you have the arrow keys to control, if you have WASD to control, you may have experienced that, A, in video games, but also maybe in some installers, maybe when you installed, you know, your OS or Linux or whatever, maybe you had to navigate around something and pick certain things, select certain things. That would have all been handling key presses. Then you have input files. This is how most scientific computing software works. Basically you have your data in a file and your program reads from that file, does some kind of computation based on the data, and then puts some kind of output, possibly also in a file. So pretty much just data, IO, and that's pretty much how real software works. Back when computers were used for actually computing things. And then also you have standard input, both typed and piped input there from the user. Again, you maybe had experienced this kind of stuff from installers and utilities. Maybe you played some kind of boomerish game where you had to like, oh, what's your name? I am Brian or something, right? You have to put your name in or maybe your host name when you start your computer up. I don't know. These kind of things would be from standard input. That's all boomer stuff. And then also piped input. This is actually somewhat useful. Thank you, boomers, because a lot of the utilities that are on Unix kind of deal with piped input. You can pipe strings into different commands and do things. So just have some uses there as well. But there's a lot of diversity as far as user input goes, but thankfully one tiny function is all you need to handle all four of them. Little caveat there, but pretty much just this one functional load is all you need to handle all those four different types of input. And that's just our wrapper for the read syscall. So pretty much this function, we wrote it a few videos ago, basically called read characters. You pass into register RDI, the file descriptor that you're trying to read from. That can be an actual file that you've opened. Maybe you have a high score file like in our game that we're opening and reading the value inside. Or it could be a standard input file. So I think on Linux and on BSD that's a zero, it's the hard-coded zero value. That's also an input file, but it's opened by default when your program starts. So the kernel opens that file descriptor for you automatically. So that's what would be an RDI. In RSI would be the memory to which you want to copy bytes to some buffer. You have some buffer of maybe four, eight, 16, a thousand bytes that you want to copy data into. And then RDX is how many bytes you want to read from the file. So it's pretty straightforward. This call, this is just a wrapper. All it does is it sets the read syscall ID number into RX and then calls syscall. It's pretty much a waste of a function, but it does make things a little bit easier when you're programming. So yeah, this tiny function is pretty much all we need to handle all four of these different types of user input. So the question is now, what's the algorithm if you can even call it that? It's a very simple algorithm. So if you have an input file, let's say you're a teacher. Let's say you want to, let's say you have all your students test scores in a file and you want to calculate the average, the standard deviation, you want to calculate percentiles or whatever, you would open that text file and you'd save that file descriptor, be it five, be it six, it's some number. You'd save that number in a register, maybe in memory. And then you would just read from that number. Read from that file descriptor, that file, some number of bytes into a buffer. Then you parse it in some way, you do your math, and you'd be done. It's a very similar story with reading from standard input because it's the same thing. The only difference is it's actually easier because you'd have to open a file. The file descriptor input is already open, open by default. So you just select standard input as the file that you want to read from, read some number of bytes from it, and profit, very simple. That works for both typed and for piped input. So that handles this, this, and this. How about key presses? Now for key presses, it's a lot harder and that's because you have to handle, well you have to basically use what's called raw mode, and I'll tell you what that is in the next slide. But basically, the story is that when you're handling key presses like the arrow keys, or like enter or backspace, one thing you don't want them to appear on the screen. You don't want your video game to have like letter arrow keys all over the place or space or Q or R all over the screen. You want to turn off echoing. And then you also want to make sure that key presses are handled as they are made. Normally when you're in the terminal, you can type stuff. If I go back, you can type crap, and then you can backspace it. You can use the arrow keys and type more crap in between. And so you have a lot of ability to edit lines before you hit enter. And so you don't want that when you're handling key presses in a game. You don't want to be able to edit your inputs. Obviously, you want to handle each input at a time. You want to handle the arrow keys. You want to handle the shoot button or whatever you have as it's pressed. So for that, you have to use raw mode. And besides that, it's the same story. All you do is you put the terminal in raw mode, then you pick standard input as your file descriptor, and then you keep reading from that input file. However many bytes, I picked four. I think that's the safe number to pick because each key press can be, I think, up to four bytes in how long that code is, and then you just process the keys. So you keep checking. Did the user push Q? Did the user push space? Did the user push the shoot button? User push, escape, whatever. Let's say Q and escape quit. You keep checking for Q and escape every time a key is pressed. And then maybe Q means leave, so you just quit. But when you quit, you have to remember one thing, and that's you have to revert the terminal back to the way it was before you put it into raw mode. The reason why is because if you don't, your computer won't, you won't be able to see what you're typing. And that's a big problem. It happened to me plenty of times when I was debugging this code, basically your code will crash, and then you won't be able to see what's going on. You'll have to figure out how to fix it without being able to see, which is a little bit of a problem, in my opinion. So yeah. Now what is raw mode? That's a very good question, Gerr. So I guess there are like a lot of different combinations of flags for the terminal, but basically the two kind of categories are cooked mode and raw mode. And actually these have almost like a preset configuration on both Linux and BSD. I'll get into what that is in the next slide. But basically cooked mode is what you're used to on the terminal. You can edit lines before submitting. As raw mode, I said every keystroke sends a unique byte string to standard input. So let's say Gerr is typing hello, but he typed it wrong. He typed helco, H-E-L-K-O, then he hit backspace twice, backspace, backspace. He fixed the L-O, then hit enter. That would send this string of bytes into standard input in cooked mode, but it would send these bytes one at a time as the keys are pressed into the standard input if you were in raw mode. So the H you can see would send a 104, E would send 101, L would send 108, K-107, backspace would send 8 and 8 twice. So yeah, you get every single key one at a time and you just be checking. Let's say you wanted to check if the user pushed H. Maybe a help menu would pop up. You just keep checking for 104, 104, 104, 104. Did the user push 104? If yes, go to the help menu. So yeah, pretty straightforward difference there between cooked mode and raw mode. Now, how do you get into and out of raw mode? This is, it looks complicated, but I'll tell you it's, I mean it kind of is, but you only have to do it once. And honestly, I already wrapped this into the toggle raw mode file. So all this is handled and you could toggle into and out of raw mode at a whim just by calling a single function. So all this is built into there if you don't care about watching this next explanation. Basically the way this works is it's a little bit complicated. So you have to read the terminal configuration before you start. So that's what I'm calling termios, I think that stands for terminal IO status, just a guess I have no idea, but basically you can use the IO control syscall. Now what is that syscall? Let me check because I actually don't know the official words here. Check the manual IO control, IO control system call manipulates the underlying device parameters of special files. So basically that includes standard input and what we're going to do is let me go back. Make sure you can see we're going to pick the standard input special file and we're going to use this get attributes, this is mean terminal configuration get attributes, I have no idea. Basically we're going to just save the initial terminal configuration for standard input and we're going to save it into the address old. I think from my experimentation, it's around 48 bytes. If it's more, I'll change it in the code, but as far as I can tell it's just 48 bytes. So we have some memory locations here. We have one called old, that's just 48 bytes initialized to zero but it's just 48 bytes of space. We have a similar address called new of 48 bytes and then we have a buffer I'll get into that in a second. But basically, we're going to read the initial terminal configuration, it's going to be a series of flags zeros and ones 48 bytes worth of zeros and ones into the memory location called old. Again, we're going to copy that we're going to repeat that process for new address. So that's going to be we're going to have two copies of the current terminal configuration. One will be in the address called old one in the address called new. And then we're going to tweak the new one just a little bit. We're going to tweak some of these flags and you can find these flags if you open up the term Ios manual, you can see what some of them mean. This is like the interrupts for break and carriage returns versus new lines. How should they be considered echoing onto the screen, new lines, all this kind of stuff. Just look up if you're curious what they mean, you can look them up in the manual they're all described. And basically this entire five lines of code is pretty much exactly what's being done by CF make draw that's a built in function on both Linux and BSD. And we just kind of copied that and put it put it here in our assembly code. And basically we're just tweaking some of those flags in the 48 bytes that we're calling new. So we've we've saved the old configuration into both old and new. And now we've just tweaked some of the flags in new. And then you can see we just set the terminal configuration to the new values. So in this case, we don't use TC get attributes, we use TC set attributes. And all these values I've I've hard coded them into the operating system dependent include that will automatically be grabbed whenever you compile this on your computer. If you're on Linux, it will pick the right values. All of these are different on Linux and BST. So it will work on Linux or work on BST just out of the box. So yeah, so basically we copied the terminal configuration into old and to new. We tweaked the new one. And then we set the terminal to the new one straight forward. Now once you've said it to that, you have to parse the input. And so in this case, I'm showing you how that works. We're just reading a double word. So four bytes worth of information, actually, we're we're clearing it out here. So we're clearing the rebuffer to zero rebuffers four bytes worth. Then we're just reading four bytes into the buffer. And again, that's because some of these key codes, I'm pretty sure are up to four bytes in length. And then that's what this lines do here is read four bytes. So you know, let's say you push the Q key. It would be your rebuffer would have a 113 in the first byte and then you'd have three zeros. So you just got to read and check, hey, did the user push Q, did the user push enter or what did the user push and just have a comparison for all these possibilities. And then jump to a certain, you know, address where you handle that possibility. In this case, I say, if the byte that you read from the rebuffer is a Q, then jump to done. And what does done done leads the done leads, I should say done leaves the program. Or it leaves this loop. Otherwise this loop goes forever. You can see it jumps to itself over and over again. If you don't push Q or enter, I guess. And then done, you're done. One thing you have to do and done is you have to make sure to restore the old terminal configuration. And that's why we saved it in the first place is because you have to put it back. Otherwise, you're going to be like Lee Sin here trying to figure out what's wrong with your computer because you will see anything because echoing will be off and you're going to have a hard time. So yeah, make sure you do this at the end and you should be good to go. Now let's check out the code. So I've got four different examples here. I have some very basic ones. This is extremely basic. This is extremely basic. And then these two kind of, you know, deal with key presses. So they're a little bit more advanced, but they should be pretty straightforward. Now that I've explained the underlying process. So yeah, let's go to that right now. So the four examples are in this example, six directory in the suppository. You can check them out. Let's go to the first one. And let's look at the, actually, let's just execute the code first. So compile and run. So what is my name? My name is Matthew. How old am I? I'm currently 28. And you can see it is a couple of things. Obviously it parsed the input. It says I hope you make it to 29, Matthew. So obviously it saved my name and then it also saved my age, but then it converted my age into a number and added one and then printed a string out at the end. So how would you do this? Maybe think about it for a minute. How would you implement this? And I'm just going to show you what I did for this. So in this case, I have a couple of included functions. We have print characters to print out, you know, characters to the screen. Also we have print int to print out the number my age to the screen. And we also have some other functions. We have strlen, that obviously computes length of a string, very simple. And then we have parse int. That's a function that computes the integer value of a string. So you pass in 28. It gives you the number, quantity 28, because remember when you type in numbers, they're not numbers. Well, I guess they are numbers, but they're not the numbers you want them to be. They're ASCII values. 28 is, you know, two ASCII characters with certain numbers. It's not 28. You have to parse that and do that in some way. So this function handles, I believe it handles hexadecimal and octal and binary as well. So it does all that kind of computations all built in. So it's pretty cool. I'm not going to go into it in this video. It's all in the subository. Then we have recharacters, which I've already explained is just reading characters from a file descriptor into a buffer. So very simple. Here you can see what's going on. It asks my name. This is just prints out the question about my name. Flushes the buffer. So it prints to the screen. Then it reads 32 bytes from standard input into a buffer called name buffer. Then it asks my age again, flushes the screen so you can see the question. If you didn't have this line here, if you commented this out, it wouldn't be visible necessarily. So we have to flush the print buffer. And then you can see it just does some computations. It prints out, I hope you make it two. Then it computes the length of the string in your age. Then it parses the number in your age. It increases that by one, and it prints that number to the screen. So in my case, I typed in 28. It basically figured out what number is those two ASCII bytes. Then it incremented it by one and printed it out. Then it prints out a comma. Then it prints out my name. My name is Matthew. So it printed that out, and then it flushed, and then it left. So very simple program and very simple operating principle. So the next thing I wanted to show you was, let's go to example D. That's also just the same thing, just piped. So in this case, this function just turns things to capitals. So you can basically... So let me compile it first. Okay. So let's compile. I have a binary now. So I can run binary, and I could type things in, and you can see it capitalizes them. Or I can actually pipe in that value. So if I... Let me... If I echo... I hate... Oh, that's the two lowercase here. I hate the antichrist into binary. You can see it capitalized. Also get rid of the spaces. I'll talk about that maybe in a bit later. But next, you can also pipe it in a different way. So let's say you run binary and you pass the value in this way. I can pass the string in this way, and it will capitalize that as well. So you can look at the code if you want. I'm running out of time, so I won't get into the code on this one. But it's a very straightforward operating principle. Again, this is just piped values in standard input. So now let's go into the example B. This is not the game. This is just key presses. In this case, I've handled... This is basically doing what I showed you before, is processing key presses. And I've processed a few, and I have different strings printed out. So let's say you push the arrow keys. It knows what the arrow keys are. If you push the space key, it knows space. I think it knows the enter key. It knows... It knows J. I think it knows capital V. I didn't do everything, but I did the whole bunch. And then I think both Q and escape leave the program. So I'll go into that right now. So how does that work? This should all look familiar to you, because it's just what I explained before. In this case, I only have four included functions, actually just really three, besides exit. I have print characters, print int. Do I even use that? Do I even use print int d? I don't. Okay. Let's get rid of that. It must have been for debugging. Okay. We don't even use that. So in this case, we're just basically using read characters and print characters. So we're just manipulating stuff in memory and reading it and printing it out. So here you can see I've saved the old terminal configuration. I've saved it again, the new. I've tweaked the new one a little bit. I've set the terminal to the new configuration. I have that loop I described. Here you can see I'm reading in those four bytes. I have the up arrow handled down arrow, right arrow, left arrow, J, capital V, enter space, et cetera. And then I have two characters here for quitting. I think both escape. I think this is escape and this one is Q. And then I have just different things to jump to in those cases. So if the up arrow is pushed, I print out a certain string, same as all the other buttons, right? So it's very straightforward. And then when you're done, let's say you pushed Q or you pushed escape, it would jump to done and then it would reset the terminal back to what it was before the program was run. So very straightforward process there. Now the game, this is going to be pretty complicated, but I think it's going to be fun. So let's go to the example C. Let's look at the code. This is long. This is a very long code. I won't get into the nitty-gritty. I'm just going to explain the overall process. I think it's interesting how this is an actual bit of software now. This is an actual program that somebody could distribute, not a very good game, but it's a game nonetheless. So I've got a bunch of includes, nothing you haven't seen before, except for these two, I think, and see move cursor. I didn't explain in previous video, basically, there's an ANSI escape code to position the cursor on the screen. So we don't have to keep printing out spaces to put our character on the screen. We can actually move the cursor to a certain location and draw our character there. Similarly, we can draw the whole and we can draw the CIA agent, you know, in different spots just by moving the cursor around to where it is. So that's how that works. And then I have this random integer function. We'll cover that in either the next or the second next video, how randomness works. I had to do something with randomness because otherwise the games are very boring if they're not random. But anyway, we'll talk about that in a later video. Besides that, it's pretty much the same thing. So you can see the first thing I do here is I'm reading from the high score file. I have a file that we're saving the high score to. And so I'm just reading from it. I'm opening the file. I'm reading from it into a buffer. I'm checking if the file even exists in the first place, etc., etc. Then we get to the part where we are printing out the main menu. So when you open the program, there's a menu that appears that says Fed Nodes 2023. And so first thing I do is I put it in raw mode. You can see here I have that function called toggle raw mode. And here you can see I'm going into raw mode. Now that when I type, the letters won't appear on the screen one by one. They will be processed as you push them. So here you can see the menu is being pushed, is being printed. So I have a couple of different things here. Basically we're going to print out, where is it? I have a whole big, yeah, here you can see I have a whole big thing for Fed Nodes 2023, just a bunch of memory being allocated to spaces. They probably could make this smaller if you really wanted to. Actually I know you could. You can save some space, but I'm not really concerned about the file size. It's only 5K, so not a big deal. And these things here, the slash E, that means escape, and this basically means color. I think this is red. This might be yellow or green, and this one I think is orange. Either way, just changes the colors of different things on the screen. So yeah, basically all this stuff is just to handle that cursor. When you play the game there's a cursor there, and this position is that up and down. Let me actually show you. See that cursor is orange. I can move it up and down. I can go up, and then overflow to the bottom. I can move down, overflow is down. So it handles that, and then you can hit enter. I think space also works on any of these options, and something else happens when you do so. So if I quit, it quits. If I clear the record, it goes to zero. That does two things. It also, first it sets this value here to zero. Also in the code there's a register that encodes the current high score. And then it also writes that file to the high score. So if I show you the high score file, you can see it's zero right now, because we just cleared it. So when I go back into the code, you can enter the function, into the game. Now it says zero. So anyway, yeah, all that's left is being handled. And then when you hit new game, a whole new operation pops up. So let me show you that now. So if you had selected on the new game option, it jumps to game. So in this case, I have a bunch of math in here. I'm not going to go into all the details about what's going on. So basically, we're just going to track, you know, your current score in register nine. Clear the screen. I have some stuff to move the, to check if we have a valid game. So here you can see I'm calling random, random integer that will, hey, where should we put the whole? Hey, where should we put the CA agent? Make sure we're not coincident, because that would be a very boring game if the agent was on top of the whole already. So make sure no one's in the same spot. Then I have a map loop. This loop is kind of like drawing the map. And here you can see this is where I'm actually parsing the input. So here, the buttons that are of interest to us are the arrow keys up, down, up, and right, as well as R that restarts the level case to get soft locked, as well as escape and queue both quit the game. So you can leave. And all those things have a corresponding bit of logic here. I'm not going to get into the details. There's just too much to talk about. You just have to know that whenever you move yourself, you have to cover up where you used to be with an empty space. And then you have to constantly check, oh, am I against the wall? Am I against the agent? Is the hole there? Just constantly be checking these things to handle the game. And that's just about game development. I'm not going to talk about game development. I mean, I don't know anything about that, so that's just on you. But this logic should be pretty basic. We have a test if you died, this is the agent died. You increment the counter for your high score. Then when you leave the game, it will see, hey, is our score a new high score? If so, save the value to the high score file and also update the value on the screen. So all that makes sense. And then when you're done, as I mentioned before, you have to, well, both clear the screen as well as return to cooked input mode. But then also, I did mention this, we are doing something with the cursor. So right now you can see on the screen, my cursor is visible. You can see that rectangle there. You can turn that off. And there's an ANSI code to turn off the cursor. Here you can see it. It's called, I call it hide cursor. Basically, it's just the escape code, question mark 25L. That will turn off the cursor. So that means whenever I'm, you wouldn't be constantly seeing that rectangle on the screen. So everything of this code, I actually hid the cursor. So at the end, I also turn the cursor back on. So that's what this line here is doing, this three lines. I'm printing out the ANSI code that would make the cursor a rectangle again as opposed to being invisible because you want to be able to see that. With that, I'm pretty much done with showing you different things here. It's a pretty cool game. Without looking into the code more in detail. I will do one thing though. I talked about that, yeah, this part here. So the reason why this wasn't putting the spaces in is because the way I implemented it. So this was when we were piping in a string into standard, just going to put into the function here. I did very, very rudimentary logic. So let me show you how the code works. So the thing is in ASCII, all the letters are together. So A through Z lowercase and A through Z uppercase. I think uppercase ones come first, but either way, they're adjacent. So here all I'm doing, you kind of can see is, yeah, you can see here, I'm basically I'm reading in the string in a very rudimentary way here. You can see I'm just, I'm reading in from standard input 32 bytes of data. So up to 32 bytes of a string I'm reading in from standard input. I'm checking the length of the string. I'm removing the trilling new line. And then also here's the loop. So in this case, I'm going through each byte and I'm subtracting 32 from it. That's because the difference between capital A and lowercase A in ASCII is 32. The problem is space is not space when you minus 32. So you should have some extra logic here that says, oh, make sure we're only dealing with letters. We're not dealing with numbers. We're not dealing with fancy symbols. We're not dealing with, because you can't capitalize a percent sign. You can't capitalize a space. So there should be some kind of logic built into this. I didn't do that because I wanted to keep everything simple, but yeah, so that's kind of what's going on here and that's kind of why you were seeing lack of spaces just because we didn't do any input pre-processing to make sure that there was nothing besides lowercase letters. Anyway, with that out of the way, we're pretty much done here. I will, now that it's been 32 minutes and all the normies have left, I will plug the Discord. We have a Fed Honeypot, no, no, sorry, Discord server. You can check the link in the description. It's secret. So check it out. All right, guys, see you in the next video.