 So, here it is, the assembler version of Wampong. So you may recall a previous video I've done a little while back now, last year's on time. One player pong game I wrote in Commodore 64 basic, just for fun, really, and also to demonstrate just how slow basic is, if that point hasn't been laboured to death already, and to demonstrate how fast assembly is, I thought I'd port it, or a version of it. There are some subtle differences. Now, it's also interesting to note that the speed difference between basic and assembler, people only talk about the execution speed, which is probably the most important thing that's certainly back in the days of the 80s. But what I don't mention often is how quick it is to write in basic. So it's kind of like an inverse thing going on there. You've got basic, which is very quick to achieve what you want in, and with a lot fewer lines of code. So here's the original basic version, I still got all the line numbers, I've just copied and pasted this in, but yeah, if you take out, there's three lines with just REM statements on. Sorry, 95 lines of code, 95 lines of code to get that Pong game. Conversely, the assembler version is 444 lines. So yeah, for me anyway, it takes me a lot longer to write programs in assembler than their equivalent in basic. But the result in code executes a lot quicker. Now I've really commented this code thoroughly, so you can have a look at it yourself, the link is in the description, and you can go through it step by step, and I've explained as best I can what each instruction does on each line. So I'll just go through it at a fast pace, high level, so that it doesn't take too long, you can just get the basics, the gist of it, and then you can go through it yourself, if you've got any comments or questions, great, leave them below, I'm always happy to read those and respond to fellow retro computing enthusiasts and whatnot. So yeah, so this very first thing here, I've coded this using Visual Studio Code on a Mac and with the kick assembler extension, and that's the thing that converts all of this into binary that the Commodore 64 or the emulator will run. It will work on an actual hardware as well, but for testing it's a lot quicker to use the emulator, vice emulator, or the debugger. So this basic upstart too just tells the C64 when it loads, it inserts some code for you that when it loads the program file, it will immediately begin execution from this label here IRQ init, which is this one, IRQ initialization stuff. Asterisk equals hex 1000, it's just the starting location in memory for our code, I've just picked nice low numbers over plenty of room, not that anywhere near all of it was used, only a fraction really, but yeah, so I just chose 1000 there, and yeah, in other assemblers the asterisk might be ORG, originate, so yeah, so that's what the asterisk is. So IRQ initialization just sets up the interrupt requests so that every time the raster line, which is the line currently being drawn to the monitor, every time that reaches the bottom, or starts again from the top, the code will execute, so only execute and only update once a frame rather than as quickly as the processor can do it. Now to give you some kind of idea of the difference, I'll try and supply some video showing basic versus un-sload down assembly, just so you can see the raw speed difference. But yeah, if the screen on, you know, a PAL screen, I think updates 60 times a second, so 50 times a second, it's 50 hertz, 60 times a second for NTSC, but yeah, so 50 or 60 times a second, whereas the process is clocked at 1 megahertz, so that, you know, that could theoretically perform, well, you say, you know, 1000, sorry, a million cycles a second, so it can execute a lot quicker than the screen can even be updated. If my maths is off, sorry, it was just to prove a point that the processor can do things a lot quicker than they can even be displayed to the screen, hence why, if you don't throttle it, it just looks, things can just run crazy fast. So we do is setting up the interrupts here. This is where we would actually put in any interrupt specific code like replaying music. As we're not, what I've done in the main game loops is just to check that the raster has reached 0 or, no, reached 255 actually, so you know, that the register that handles the position of the raster line is at the max. If it's not, it waits for it to get there, then it'll execute, and the next time it goes, it hits that bit of code, it'll wait again, and that has the effect of just running the code once per screen update. This little block here, I mean, I've put the detailed description, but when we leave the main code of the program, we might have, it might have values stored in the AX and Y registers that we're using in our program, and we don't want those to get lost or, you know, or changed by the, you know, the interrupt, the interrupt code. So what we do is we push them all onto the stack, and we can only push the accumulator onto the stack. So you push that on, then you have to transfer the X register into the accumulator, push that on, then transfer the Y into the accumulator, push that on, do what you're going to do, turn off the interrupts, pull all that stuff back off of the stack, put it back in the relevant registers, and then hand control back to the Commodore 64 to do whatever else it was doing also, before we have to call the interrupt again. Now initialize the screen, just set the border background to black, and the text to white, clear the screen, and reset the X and Y registers to zero, so we use them in loops, and use the SID chip to initialize and get that working to produce some random numbers, and they're only used to determine which direction the ball is going to travel in at start, so there's no advantage to player one or player two. The ball will start moving up and left, down and left, up and right, or down and right, so we need random numbers for that. Then we've got loops that display text to the screen, so X will be at zero, because we just set that previously, so I'll just go over one of the loops here, they're all pretty similar, and then we're going to load into the accumulator the first byte of whatever is in intro text, which is just some text further down, I've separated the code into code at the top, text stuff kind of in the middle, and sprite data at the very bottom. Yeah, so what we're going to do is load into the accumulator the first bit of text from intro text with an offset of X, which will be zero. We're going to compare what's in the accumulator to zero, because we terminate each line of text with a zero byte, so that we'll know we've reached the end of it. You can compare it to the width of the screen, so if we've reached 40, because it's 40 columns wide, you can then move on, but I think it's tidier to do it this way. Okay, if we haven't reached a zero in the accumulator, if we have, sorry, reached a zero in the accumulator, we're going to branch off to weight key, which is this little routine here, which just waits for a key to be pressed once we've displayed the text to the screen, the intro text, which just says welcome to one-pong assembly style, percentage key to start. Okay, so assuming we haven't reached zero yet, we're going to store whatever is now in X. Oh, sorry, in the accumulator, sorry, into the very first byte of screen memory, which is at X 400, with an offset of X, which will be zero to start with. Then we're going to increment X, so X will now have a one in it, and we're going to use that one to store the next byte. So we've got the zero byte of the text in the intro text, then we're going to have the first byte or byte one. As long as that's not a zero that's been loaded into the accumulator, we're going to skip over the branch to weight key. Then we're going to store that in the very next bit of screen RAM, so it'll be memory location 400 with an offset of X, which will be one, so that would in effect be 401. Then we're going to increment X, so there we go. We'll loop over that text until a zero byte is put into the accumulator. Then it will just, oh yeah, and then we'll branch eventually where we to weight key. These again here might seem odd that we put in the routine for the display player one, we're message and player two, we're messages here, but as I have tried to keep the messaging stuff, the stuff that deals with display messages screen in one area. Weight key just does that, it resets the X and Y ratios so we can use them again and uses the inbuilt kernel routine at memory location hex FFE4 to check for a key press, compares that to zero, if it is zero, it's going to repeat the loop. So it will do nothing until a key is pressed, doesn't matter what key. Then we're going to use some more loops to write the one Pong logo if you like and the main play field to the screen, because it's all just characters output to the screen. Then we're going to draw the sprites, set their position on the screen, enabled the double height of both sprites, both bat sprites, that is just because the game was pretty quick and unless you was really on it, it was very difficult to hit the ball with your bats. Then we show the sprites, then we jump to the routine which uses around a number to decide which way the ball is moving, which is down here. All it does is it gets a number into the accumulator and it'd be a number between 0 and 200 or 0, 1, 9, 9 and that is easily divisible into four. Four blocks of 50, could have been another number, could have been 50 into, sorry, 100 into blocks of 25, but whatever, I chose 200. And I've had to put this here in between the loops that actually run the game if you like, ball moving down and right is one of them because you can only branch like 127 bytes one way and 128 the other. So the code started to grow. I was getting errors where I couldn't branch that far. So I plunked this right in the middle. So this is a little out of order, but there are pretty clever ways of achieving it and keeping the code in it in kind of a better order for readability. But hey, it's all about getting the job done. So we've decided if the number is between 0 and 32 hex, which is 0, 49 decimal, we're going to branch here. If it's not, then we're going to branch, you know, if it's up to decimal 100 or decimal 99, we're going to branch here, but it's not between that and it's up to decimal 149 or 150, we're going to branch here. And if none of them are true, we must be in the last section of numbers. So 150 to 200 or 150 to 199. So it's just a straight forward jump then or a go to. So if all else has failed, all these compares are not true, do this one. So that's why there's only three compares and the default will be down and right. Let's just look over one of these loops. It's split into four. They're all pretty identical. Just they deal with the ball moving in a different direction and therefore you're looking for a different bat to be hit. So the ball is moving left. It's only going to hit the left bat or the left edge of the screen. If it's moving right, it can only hit the right about the right edge of the screen, but it can hit the top and bottom traveling in either direction. So first thing we do is load the accumulator with the current position of the raster line. Compare that to 255. If that's false, it's going to repeat in this loop until it's true. So we make sure we wait for that screen to finish being drawn. Then we'll continue. We load the wire register with a one because depending on where we've been sent from, we might want to go back there. So if I load the wire raster with a one, I then know that it can only be loaded with a one if we was in the ball moving up and left. All right, so I know where to jump back to. Then we're going to check for key presses. There's a little routine that does that. Then when we come back, we're going to check for a collision with the left bat. Then we're going to decrease the X and Y position because we're moving up and left. So we're going to decrease the X and Y position of sprite two, which is the ball. We load A with a new value of the Y position. And we're going to compare that to 102, which is the top of the play field. If it's hit that, so this branch would be equal. That would be true. They're going to be moving down and left. So it was moving up and left. It's hit the top. Now it'd be down and left. Then we're going to compare the new value of the X register with 20 to see if it's hit the left hand edge of the screen. If it has, player two will have one. If none of these are true, so if the ball is just in empty space, it's moving up and left and hasn't collided with anything. Then we're just going to jump back, keep looping until we branch away. So let's deal with a couple of these branch ways. Check for key press. Where's this? Probably right the way away because it's a jump. You can, I didn't have to be so close. So use the built-in counter. Excuse me, check for a key press. If it's 87, that means W has been pressed and we're going to move left bat up and right bat down. If it's 83, it means S has been pressed, which means we're going to move left bat up and right bat down. Oh, so S would be strange because I think it was just working what it should be. So S would be left bat down right bat up. Okay, let's save that. Right, it's okay, a bit of live editing now. I don't know what went wrong there because it was working when I was testing it. Yeah, maybe I've done an undo one too many undoes or something else. I don't know, whatever. Right, so if you hit W, what this does, we jump to this routine and what happens here is we're decrementing the Y position of sprite zero increasing the Y position of sprite one. Why are we doing it one, two, three, four times? Because I don't know if it's a feature of the device simulator or my core programming, but it would only register key press with each press. So it was almost like on key up. So maybe it was a different routine for key down. So you had to repeatedly tap W to get the bat to move and just doing it one pixel at a time. There was no way it was quick enough to meet the ball. So I just copied and pasted it a few more times so that the bat would move quicker and move by greater increments, greater chunks if you like, except move four pixels at a time so you could keep up with the ball. You could do a loop and oftentimes if it is something this simple, it actually is less cycles to copy and paste the actual instructions. So we move the bat. Let's see what else do we do here. We check for collision with the left bat. So we load the accumulator with the values in the collision detection register, the sprite to sprite collision detection register, so it'd be the ball and the bat, which are both sprites. We compare the bits in that. So we're looking for bit one and three to be on. If they are, then we know we've collided with the left bat. We branch off to the routine that deals with that. Balls hit the left bat. Okay, so this is why we needed to know where we would come from. So if it's at the left bat and we were coming from two, which must have been ball moves moving down and left, then we're going to branch off to now the ball's moving down and right, so we've bounced off something on the left-hand side. Okay, player two wins. Let's see here, whichever player. So player one wins if the ball goes off the right side of the screen or hits the right edge of the screen. We clear the screenable text. We load the accumulator to zero, turn off all the sprites by storing that zero. All the bits will be zero in D015, which governs which sprites have been displayed. Then we jump to the routine. It deals with displaying the player one win message. The player one win message is down here. Player one wins. Press any key to restart. Now the P1 and the P2, the P1's in red and the P2's in blue. And that's dealt with. I'll just quickly point that out because when we're going through the screen loops here, we're here on the game setup. I do it because the P1 and the P2 on the main play field are red and blue, respectively. So what we also do, we push whatever's in the accumulator onto the stack because we're using that to read in text. And we don't want to lose the position with that. So we hold it on the stack and then load the accumulator with two for red. And it's the number two, but when you store that in the color memory, color screen memory, it denotes a red text. And then we store that in D800. So screen color memory starts at D800 plus 243, which is where the P1 is and 244, which is the number one. Then we store six, which equals blue and we use these offsets. It's on the same line a bit further along. Then we pull back whatever we pushed onto the stack and put it back into the accumulator and carry on. So although it's effectively through this loop, it's changing the screen color multiple times. It doesn't matter. You know, assembly is so fast we can afford those cycles. There might be a more clever way of doing it, but I don't know it yet. There we have it. That's the game. And what will happen is you display that message. Where are we? So player one wins. Here we go. And it will then branch to Wakekey. Wakekey has hit the whole thing. It starts again. The screen's displayed and the game restarts. So there we have it. Play around with it. And if you've got any comments or questions, please leave them in the comments and I will get back to you. Thanks for watching. See you next time.