字幕列表 影片播放 列印英文字幕 (train horn) - Happy back to school. It is August 31st, school for me starts next week. On The Coding Train, I am going to attempt to do a classic again. I am going to attempt to do a coding challenge and pick the Snake Game. And yes, I have done this before, but I'm going to do this in order to celebrate something that was announced today, the P5JS web editor. (audience cheering sound effect) So the P5JS official web editor is out and I'm going to try... The nice thing about this is when I code this, at the end of it, stop with the sound effects, at the end of this, you will be able to just go directly to this URL, which I will include in the video description, and hit duplicate. You'll have to make an account for the web editor, hit duplicate and then make your own version of it from my code and share that with me. So I will include all about how to do that in the video description and I'm going to give myself about... Oh, my watch isn't on. There's not timer but I actually have to go, I have to be home very soon so I'm going to give myself about 20 minutes, which of course, this is going to take longer than that but let's see how it goes. All right so however long this video is, is however long it takes. Hopefully, there will be no edits. Everyone once in a while, you know, there just has to be an edit 'cause the whole system crashes. I better get coding. (deep exhale) Snake game. Have you ever played the Snake Game? The idea of the snake game is there is a canvas, you are a dot, like a little square on that canvas. Another dot, or another little square, appears, which is a piece of food. You want to move, you can only move to the right, up or down, along, grab that piece of food. Once you grab that piece of food, a new piece of food appears somewhere else and you get a little more on your tail. You get another piece of your body and you get the next piece of food and it gets longer and it gets longer and anytime you hit the edge, if you run into one of the edges, you die, the game's over. Or if you hit another part of your body, and it becomes much harder as your body gets longer and longer. That's the snake game, that's what I'm going to code. So in order to do this, I'm going to use object oriented programming. In my previous version of the coding challenge, I used like this constructor function thing and now we have ES6 classes. I want like a happy sound (horn blares). So one thing I'm going to do is I'm going to show you... Andby the way, if you want to know all about the P5JS web editor, check a link to a Medium article that is in the video's description and a whole bunch of videos from the Processing Foundation. THere's a nice video with Cassie Tarakajian, who created the editor, describing all of it's features. But one feature is that I can go over here and find this little (tongue clicks) thing, this little tick, less than, greater than sign. Whatever it is, and I can say, "Add file." And I'm going to add a file and I'm going to call it, "Snake.js" and now, I have a file called, "Snake.js" where I can create the snake class. Now, if you've never done object oriented programming before, the idea is that this is a teth-link for this snake object that I'm going to make in my code. And it has a constructor function. I will refer you to my tutorials about broken object programming ES6 classes. Now... But in order for... I need to actually go into index.html so that the page, when I run the code, is actually using both sketch.js and snake.js. So that's there. So now I have sketch.js, which is just a... Oh, and I need to see this. A 400 by 400 canvas. And snake.js, which has nothing in it. But what I'm going to do is I'm going to say, let snake, and end setup, set-up being where the code starts in a P5JS sketch. Snake equals new snake. So I've made a new snake and what do I want to do? And I want to update the snake's location every frame and I want it to display it. So the idea here is I want to say Snake.update and I want to say snake.show. I'll use show as meaning show yourself, display. Hmm, what is this? Snake.update is not a function. Oh come on, I have to write the code for it? Yes, I have to write the code for it. So if I'm going to write a function called update, that means I have to put a function in the snake class called update and then I have to put a function called show. Ta-da, I'm done. Coding challenge complete, snake game not all. So this is like the skeleton of the code but I actually need to put stuff in here and I'm going to hit shift tab to tidy up the notation. Okay, what do I need? So I'm going to do something a little weird. I know that what I need is an array because even though the snake's position, you could think of as like a single X,Y location, I botched this the last time I did this. Really, what I want is an array because I want to keep track of a list of all of its locations. And maybe the first element and probably, more easily, the last element of the array. When with only one thing, is it's the sort of head of the snake, the front part of the snake. So I'm going to do that. I'm going to say this, I'm going to call it a body, is an array and then I'm going to say, this.body index zero. I'm going to put one thing is createVector. Now, createVector is a function in P5 that creates a vector object and that vector object has an X and a Y. So I'm going to create it at zero, zero. And then in update, I'm going to say this.body index zero.x plus equal this.xspeed I'll call it xspeed or xdirection or something and this.body.Y, index zero.y equals this.ydirection. Oh, this is so ugly but it's the way I'm doing it, right? Instead of having... It's a little weird like really, it's just one thing that's moving around, I just have an X and a Y. Instead, I have a body index zero X and a body index zero Y because later, imma get to add more pieces to the body. So if I run this, what's this.xdirection, this.ydirection? Doesn't even exist so those are going to be variables that tell me is the snake moving to the right or left? Is the snake moving up and down? So those values, this.xdir will start them at zero. (deep exhale) So there's zero and so... And then show. I am going to do something, I am going to say... What do I want to do? Showing the snake is drawing a rectangle. I'm going to do something a little bit weird. So I'm going to draw... Again, eventually, this is going to become a loop 'cause I'm going to be drawing all the pieces of it. But let's just start with putting in the X and a Y and I'm just going to say, I'm going to make it 10. This is going to be a little weird. Actually, 10 by 10, the rectangle, and I'm sorry that you can't see all the code, let me... I'm not used to doing challenges with the web editor so let's see if I can make this a little bit wider. There we go. So okay rectangle and then say fill zero, just to make it black and look at that. Already, I see that... I better save this. Oh saved a minute ago. It's auto saving, that's wonderful. There it is right there. Let me make X direction one. (groans) Oops, two equals by accident. Look, it's moving now, right? Because the ID in update the body's, head's X location is increasing, right? If I made this 100, it's moving really, really fast. 10, it's moving pretty fast. If I change this back to zero and make this one, it's moving down. I could do all sorts of weird things, make that five. But that's not, no longer in the snake game. The snake game, by definition, I can only move horizontally or vertically. So I'm going to start these at zero and then I'm also, just right now, I'm going to change this to one. I'm going to explain, I'm going to do this in a funny way that I think might work well. It's a tiny little dot, a one pixel dot, that rectangle. I am going to now, in sketch, I am going to have the keyboard be the controls. So I'm going to say, keyPressed. Oh, I forgot how to do this already. (chuckles) So if key equals up, what is it in P5? Basically keyPressed is an event that whenever I press a key, this function happens, that I can check what key did I press. But I totally can never remember how to write this function. So let's try and look at the P5 reference. Somebody maybe in the chat will tell me. Let's look at keyPressed. If value equals zero, no. If keyCode equals left arrow. This is good, this is what I want. It's the keyCode. So key is for which character I press but if I'm using the arrows, I need to use left arrow erase. Let me actually just grab this and I'm going to say if keyCode, and I know I can use this switch statement. I'm just not going to right now, okay? (sniffles) Please don't, don't at me. That's what I'm supposed to say. Somebody taught me that who's younger than me. (chuckles) keyCode equals left arrow, keyCode equals right arrow, doing a terrible job at paste, paste, down arrow, up arrow, ah. Don't hide. Auto format, there we go. Okay. So now what I want to do is if it's the left arrow, then snake.setdirection this is the way I did it before, I remember. Left is negative one to the... Negative one along the X and zero along the Y. So I'm going to write a function that's like saying set the direction. So this is for left, this is for right, this is for... (moans) I'm so freaked out. Down, I'm remaking this video, why? This is for up, negative one. Okay, tidy code. So I think this is the right idea. Depending on which key I press, move the snake in that direction. So now, I can say... I can write this function, setDirection and I'm getting an X and a Y and I'm just sayinf this.xdirection equals X, and this.ydirection equals Y. So whatever comes into the function sets those things. The people, I can see the chat over there, "Please do a switch statement." No, duplicate my code and change it and make it better, use a switch statement. Okay. So now, I'm going to do this, lets zoom in, here. Let's see if this works. Come on, no, I have to click 'em. Yes! Down, look at this. So now it's working, right? So I'm pressing all the keys and I'm moving it around. Perfect. Now, I need to deal with something. I don't want to have to zoom in, it's so tiny. So ultimately, there's an issue here. I want to think about the snake in units of one. Here's my snake, it moves one pixel over, it moves one pixel over, it moves one pixel over. But I have a canvas that's 400 by 400 and I probably want to draw is as a 10 by 10 thing. So I want to have a variable, I'm going to have a variable, I'm going to call it R-E-Z, short for resolution. Can you see that? And I'm just going to make that 10. So that variable, and actually, to be honest, I could just use, you know what I could do? I could just use the scale function. I'm just going to use the scale. So I was going to use math and I was going to always multiply it's X location by that resolution. And use the resolution for the width but the truth of the matter is P5 has a scale function so I'm going to say let resolution equal 10 and then in the draw function, I could just say scale by resolution. And what that's going to go, and notice like see, look at that, I can change this to 50, I can make this 10, I can make this 100. It's just scaling it up and if I move this to the... And so now, if I make this 10 and I move to the right, it's actually moving every 10 pixels, also, 'cause when it changes by one, it moves 10 and I could... What I might want to do is, you know, normally, I want the animation to go really fast but imma change the frame rate to five, just to slow it down so we can what's... What's a little tricky here, when using the P5 web editor is I need to click over here to give this preview page focus so that it gets the key commands. So now you can see it moving over. And this, you know, this can... The frame rate, typically, I don't want to slow the frame rate of the the animation down. But this is a way I can control the speed of the sketch. So this is what I'm going to do, okay. (deep exhale) So now, I need... Ah, okay. So I need to have a food, a piece of food. So I'm going to make the piece of food just a vector. At some random... I could make a food class but I think that's overkill. I'm going to make it a... Ah, okay. So here's, now, a tricky thing. I want the food... Okay, let's say my sketch is 400 by 400 and really... So really, my world is 40 by 40 but I scaled it up by 10. So I need a variable to keep track of actual dimensions of the world. So I'm going to say... I'm going to use W, well, I don't know. I'm starting to think of columns or... I'm going to say W. Equal... I'm going to actually just put let W and let H, here. So I'm going to say W equals floor width divided by resolution. So what is this? Width divided by resolution is 400 divided by 10, which is 40. And the reason why I'm using floor there, is just in case my math is off, I want W to be an integer, a whole number, that will take off the decimal place. H equals floor height. And of course, it's a square, so I don't really need to add the two separate values but your window might not be a square. There, okay. So... So now that I have that, why was I doing this again (chuckles)? Oh, the food. Because now, I want to get an X position and guess what? I remember this from before. I'm going to write a function, food location, foodLocation. And what happens in this function is I pick an X, which is floor random W and a Y. Floor... So I need a spot, random H, right? I need to find a random spot for the food and set that there. And then in draw, I'm going to make the food a red, I might be standing a little in front of the code but hopefully this is okay. And I'm going to say rectangle food.x food.y one, one. So you know, I guess I could make it a point or something but it's just everything is of unit one scaled up. Okay, can I... Oh and I need to actually call at the beginning, foodLocation. And Y, oh, it's not... You know what I need to do here? Is I need to say noStroke. That stroke is getting scaled in a strange way so this is actually 10 by 10 pixels. It was much bigger because with a stroke there, that's getting scaled as well. So now we can see... Woops. If I click over here, we can see and eventually... Come on, get that piece of food. Okay so now, good, so this is working. Ah, the snake game is working. Oh, this is good. This is much better than I did before. (chuckles) Helps to do this a second time even though that was a couple years ago. Let's make this 20 just because I want to be able to see it better. Okay so that's good. Let me just make sure things are lining up, excellent. So now, I need a test. I'm going to say snake.eat food. So that means I need a function and let's put that... I don't know if the order matters so much. I'm going to put this here. And I'm going to say, eat food, I'm just going to make an argument called position so basically, the food is getting passed in here and I'm eating the food if now, this is a really, probably a bad idea because you never know, in java script, if you've really got the number like three or if by accident, you have the number 3.00000001 (laughs). But I'm going to test, in theory, if the X, Y of the head of the snake is the same as the X, Y of a piece of food. If they're equal, I should be eating food so let's... Let's just see if that works. So I'm going to say if... Well first, let X equal... Let's just put this in a separate variable. Let Y equal this. I'm going to get that head of the snake location. If x equals pos.x and y equals pos.y, then return true and also say console... Console, I'm going to say print. I could say console log print food eaten. Okay so let's see if this works. It's a little bit dangerous, a little treacherous and I'm going to say return false, otherwise. And then, in the sketch, I'm going to say, if... If, I'm going to have this return something, if you eat the food, then you just need a new foodLocation, right? So immediately, you should pick a new random location. Okay, let's try this. (scoffs) I missed it. Ah, I can't get it. Oh, so that's working. All right so I think all my flooring of the numbers, you know, maybe I need to have some better... This seems to be fine. So I'm going to live with that. I'm happy with it. Okay so now, we are so close. What do I need to do? This is going remarkably well. (knocking) That's a sure sign for something to go wrong. I'm going to say this.grow. If I eat the food, I want the snake to grow. If I'm calling a function that I intend to be part of the snake object, within the snake class, I need to reference it by saying this.grow. So that means I need to write another function, grow. And what that means is I want to expand the array. (deep inhale) Okay, okay, ooh, this is the tricky part (groans). This is where everything went wrong before. I'm not even looking at that chat, there's a chat going on, I'm sure everyone's screaming at me. (chuckles) I'm going to add another variable, this.len for length, length of the snake and it starts as one. So if that, at that a minimum, I know when I want to grow, I want length to go up by one. I want the length to increase and I need to add something onto the array. Add something to the end, add something to the beginning, this is unclear to me. So first of all, let's try... So let's just try saying this.body.... So first, let me get the last... I'm thinking about this. You know what? I think this is going to be... I think, actually, this is simpler than I think. Let's just try saying this.body.push createVector. So I'm just going to push a blank vector into it. I know I need something else in the array so let me just... And push it so that it adds to the end of the array, maybe I want to put it at the beginning. I'll figure that out in a second but I'm just going to push at the end of the array. And actually, now, I'm realizing the flaw in that but that's okay. Yeah so actually, so pushing... I could put it in the beginning and that would actually work, I think. But okay, we're going to figure this out. I'm trying to think it through so it's easier to think it through, just code it. So what I want to do now, this whole thing... So first of all, what this mean is anytime I want to draw the snake, I don't actually want to draw... I don't want to draw just a single rectangle, I want to draw all the rectangles. So I'm going to say... I could use like a for of loop or something but I'm just going to say, I'm just going to use a regular, old fashioned this.forloop, this.body.length, I plus plus and then this loop will go around here. Uh-oh, oh no, oh no. (buzzer buzzes) Oh no! I've killed the editor. Let's see when it last auto submit. I'm going to quickly click off this auto refresh. And where did I last leave off? (yells) That's not so bad. So the last time it auto saved was here and I am going to... I'm going to retype my loop more carefully without having auto refresh on. I is less than this.body.length, I have to more this, now, back all the way over here. I plus plus and I'm going to auto format that and I'm going to say this.body.i so now I'm drawing every element in there. Okay. So now I'm going to hit save. Project is saved so I have that. Oh, I lost what I wrote in grow (chuckles). Live and learn. Move this over here. Oh, oh right, 'cause I had this open, that's why. So this actually comes back here. So grow, what did I do in that? I said this.body.push createVector and I said this.length. I don't know if I need this length variable, to be honest 'cause by definition, the length of the array is the length variable. (chuckles) I'm so sad. And then did I add that? Then I added that. Let's see, I lost some code. You're not working... You're not really coding if you don't lose some code every once in a while to an infinite loop. Okay. So here, we're good. Now this... What's going to happen here is really weird but I'm going to get the food. Oops, shoot, I missed the food. So interestingly, why am I not seeing... I would imagine that I would see another... So one thing, I would guess that I'm going to see another appendage but it would just be at zero, zero. Oh, look at me, look at this, push createVector, createVector at zero, zero. Let me just do that. So that, I forgot. So what we're going to see here is this. There we go. So that second part of the snake is there but it's up there. So what I need to do now is when I'm updating the snake's location, I'm going to comment this out for a second. The first thing that I want to do, and there's shift, okay? There's an array function called shift. I forgot one. Last time I did this, I didn't use that, I just manually moved all the spots in the array. Let's look at array javascript shift. I don't actually know what this function does but everyone was telling me I should do it. Shift. Oh, look at that. That's so perfect. So this is what I want, I want to shift all the elements down one and I guess I'm losing one. So let me just see about, let me see if I understand this. So just in the console, let me practice this. Let array equal five, six, nine, 100, five. So that's what in the array and if I say array.shift, woops, no. That's what it returns. Did it shift it? But it shifted. Oh so it's giving me the thing that it got rid of and then it's making it one less. So this is perfect, this exactly what I need. Oh, I love it. So actually, all I need to do, every time in update, is say this.body.shift and then that's moving everything over and then the last spot so one thing I need to do is let me save where it currently is. So let me pop... Pop doesn't remove it from the array, right? So if I say array.pop, that gave me the element five but the array still... Oh no, I got rid of it. How do I get something off the end without removing it? Oh, un-shift adds from the beginning. That's interesting. How do I get the last element without removing it? Well, I'm just going to do it the manual way, this. I'm just going to say let head equal this.body. And this.body.length minus one. That's the last element .copy. So this is me taking the last element and making a copy of it. And then what I want to do is say head.x plus equal this.xdirection head.y plus equal this.ydirection and then I just want to put that back on. So I want to like save where it was and then move that and then the body is all there. Yeah so I'm being told this is actually how to do it. So that's the last element, copying it. Let's... So one thing that I want to do, what's wrong here? This.body index this... Cannot read property copy of undefined. So do I not have... Oh, because I'm doing it after shift. And if I'm shifting something with zero... Yeah, if I'm shifting something with zero... So shift has to happen after, right? 'Cause if it only has one thing in it, I shift it, it's gone. Okay. So this is working, now. Oh look at that, so this worked. (laughs) And it made it longer but it created it back at the start so I need to keep that location when I grow it. So the same thing I need to do here, in grow, what I want to do is not add. What I want to do is do this exact same thing and then push, right? I want to take the last one and add it to the end. So I need to just duplicate where it was before to the end. I think this should do the trick. Yep. Is it... Am I going one step behind by accident? I'm not sure. That's pretty good. I don't know why the food... Oh. Oh, you know what? It's not getting in until I get to the back. Why? Because eat is always checking this one. I need to check the last element. Same thing, this is very awkward, the way I'm doing it. And I'm sure somebody will make their own version that's less awkward but I need to check the location of the head against the food. Here we go, let's try this one more time. Snake game go. There we go and up. Yeah, this is better. Okay, great, this is working. Yay! All right, now guess what? One more thing. I need to know when to restart the game. When do I die? So I'm going to do... I'm going to do a function called like check, you know, check for death or like I don't know, endGame? And what I need to do is I need to check if the head intersects with any of the other positions. So the head, as we know, is... Well, it's this. This is a little bit weird, what I'm doing. That's the last spot and now I can say, for let I equals zero. I is less than this.body.length minus one. I don't want to check the head against itself. I plus plus. I'm going to say let part equal this.body index I. If part index... If part.x equals X and part.y equals Y, then return true. The game is over or if X is greater than what was it, W? Minus one? Or if it's gone off the screen or X is less than zero, Or Y is greater than H minus one, or Y is less than zero, then also return true. We should probably check that first. 'Cause if you're off the edge, I don't need to check... I don't need to check any of the body parts. (deep inhale) Ah, oh but I need to first get the X and Y values. So I'm doing something a little weird but sometimes I'm using the vector and I'm saying pause that X, pause that Y, as soon as I'm pulling out the X and Y from the vector. And then at the end, I'm going to say, return false. So let's see if now, I can get endGame to work. So in here, I'm going to say, also now at the end, if snake.endgame, I'm just going to say print. Print end game and I'm going to say background 255, zero, zero, and noLoop so I'm just going to completely shut down the P5 sketch if the game ends. So this is not necessarily what you want to do for like interaction design but I'm just testing the feature. So the first thing I'm going to do is just try to go off the edge. So I'm going to go off the bottom. Yeah, okay so that works, so going off the edge, at least the bottom. I should probably test the other edges. Oh, what just happened there? And I took my auto refresh off. I'm going to put that on. So if I go off the top, ooh, that didn't work. I probably had a mistake somewhere in there. If oh, Y, this should be Y. Woops, that was a mistake. So (groans), all right hopefully, that fixed that. I'm going to... And now what I need to do, so one thing I need to do, just to be able to test better, is I'm going to add something, I'm going to add the mousePressed function And I'm going to say this, snake.grow. So anytime I click the mouse, I'm going to grow the snake so I could do it, sort of test this feature. Oh, woops, shoot. Oh and you know what? I should set the snake in the middle. So the snake's location should probably be, initially, W divided by two and H divided by two and I've got to keep everything an integer. So I've got to put floor in here, just in case. All right. So now, let me... Whoa, why did that... Oh. Oh because when I grow, the head becomes the same location as the other part of the body. And then I check to see if the game is over. Shouldn't it fix it with... Oh but if it's not moving... So I got to move it first. I mean this should be okay (chuckles). No because if I click, I have to get focused. Ah (deep sigh). This is like a silly debugging thing. So what I'm going to do is I'm going to have it grow with the space bar, not mousePressed. Else if key equals space. I'm just trying to do this to debug it and so I can do whatever I want. And I click over here and I move it, so I'm going to make it longer. So now let's see if it dies, yep. Okay, great (loud clap). So (bell dings) I think I finished this game. Really, what I want to do is come up with an end screen or restart the game or have some sort of score. Let's see, before I go, how long I can play it before I die. And oh, let me just show you something. If I go, I'm going to hit save. If I go under... This is a feature of the web editor. I'm going to go file share, I'm going to go here and I'm going to grab this full screen URL, I'm going to grab it, I'm going to open a new tab, and now it's just the game. I'm going to make it bigger for fun times. I'm going to click it here and ready? I'm going to play the game. Goodbye, make your own version, look for the link, sign up for a P5 web editor account, hit duplicate, make your own version, share it in the comments. There will be a codingtrain.com page, which you could also share it. This is much too easy, I'm going to be here forever. I'm going to look at the... Well, this is working (chuckles). I made the game. I can go home now. Let's just see how long this goes for. (yells) Okay, I died. I don't know why I died. Oh, I hit backwards. (bell dings) (buzzer buzzes) So good thing I hit backwards (trumpet fare) the game is over. Thank you, thank you. So that might be a bug that I want to fix. So that actually, if I hit back... But I guess you shouldn't lose the game if you hit backwards. I don't know. You know, you can make the design more interesting, you can do all sorts of things. You can now make your own version of the snake game in P5, in the web editor, instantly share it. Thank you to the The Processing Foundation, Cassie Tarakajin, all the people who made contributions to the P5 web editor, and all the people who have worked on P5JS over the years. I'm so excited to be able to make tutorials and coding challenges with this. I'll still use my other workflow and I will use processing but I hope you enjoyed this video and I will see you (train horn) next time on Coding Train. (upbeat electronic music)
A2 初級 編碼挑戰#115:蛇遊戲重裝版 (Coding Challenge #115: Snake Game Redux) 2 0 林宜悉 發佈於 2021 年 01 月 14 日 更多分享 分享 收藏 回報 影片單字