字幕列表 影片播放 列印英文字幕 (train whistle blows) - Hello and welcome to a coding challenge. In this coding challenge, I should probably go eat lunch but I'm going to just try this coding challenge. I am going to attempt to program this. This is a flocking simulation. What you're seeing right now is an example that I made many years ago of a flocking simulation, a Boids system running in processing. I'm going to try to make exactly this or close to it in JavaScript with the p5 library starting from no code at all. Obviously besides the dependencies. Okay, so this is the example running. Let me just give you a little bit of background here. If you want to learn more about the flocking algorithm as invented by Craig Reynolds, you can go to this link to read this explanation of it. And find many other links and resources down here, whoa, that's a lot of stuff. Have fun, see you in a few weeks. You can also check out this YouTube video of the original 1986 flocking simulation. Wow, that's amazing. This is so cool, oh my God, I love that. You can also find an explanation of it as part of my Nature of Code book online with some diagrams and code written in Processing and an exercise that you can try which I'll refer to back at the end. But I'm going to start from this, a blank slate. I just have the canvas, you have some amount of pixels in it. There is nothing happening in the console. I've got this code set up and draw. So I'm going to write this code without worrying about being perfectly organized and being able to scale it very easily. I just want to get the algorithm working but of course, where is the button? (pop music) If you want to see a refactored example of it, you can probably go and look and actually, at my finished version in the Nature of Code which I'll also link to. The first thing that I want to do though is I want to add another JavaScript file called particle.js. Actually, I should rename this to boid.js. What is this term Boid? When Craig Reynolds invented this algorithm for simulating a flocking system like a flock of birds or a swarm of bees, I suppose he didn't want to use the term bird, but it is kind of like bird, so Boid, it's kind of like droid but Boid. Go read the original paper about steering behaviors and flocking systems and you'll find out more about the history about this. But I'm going to make a class called Boid and I am going to use the p5 vector object. I'm going to make Boid objects and each time I made a Boid object, I need a constructor and I am going to give each Boid a position. I'm going to give each Boid a velocity. I'm going to give each Boid an acceleration. All of those are going to be vectors. I'm doing this is 2D. You should, after you watch this, if you watch this, you should make your own 3D version of this. All of the math will work exactly the same way but you'll just need to rethink how you're visualizing this. Just to give things a start, let's just put the position in the middle of the window and let's write a function called show which will just draw them as a circle right now. Actually, I'll just even use point, like strokeWeight 16. Stroke 255, point this.position.x. I think Visual Studio code will do this for me. No, where does it? It fixed that for me once, whatever. This PointerEvent, I'm not a PointerEvent, I'm a point. So now, if I were to then here make a flock, which is an array, and I should probably make a flock class, that might be a thing to refactor later. But I'm just going to say a flock.push new Boid and then I just want to say for every Boid in the flock and I'm using of, I'm using a for-of loop but I said in because that's the way I feel right now. A boid.show and then I need to make sure I am also referencing boid.js in my index HTML so I have the basic Boid class, which just creates a position, velocity, acceleration and draws it as a point and then I have my sketch where I make one Boid and I draw it in the center of the screen. There it is, there's my boid. (bell rings) Step one complete. I probably should plan this out but I have one Boid, that's good. Now, let's have that Boid move. Let's actually write in the Boid object a function called update and this I probably covered ad nauseum in a lot of other videos in my whole Nature of Code series. But the idea here is that position is updated based on the Boid's velocity and its velocity is updated based on the Boid's acceleration. If I now actually create a p5.vector, a random 2D. I believe this is a function that will give me a random velocity vector and I now go in here and say boid.update. Oh I just spelled it wrong. I thought I missed the this dots. Here we go, look, it's moving. It's moving with a random velocity each time. Okay, now let's just make a bunch of these. Now I have a system of 100 Boids but it's interesting, look at this, it fans out in the perfect circle. Do you know why that is? Because I make a Boid and I give it a random velocity. That random vector is always of unit length one. The direction is something different so they're all actually moving with the same speed. If I wanted them to have, and this is not an important detail to the flocking stuff, but just to sort of get a sense of how this vector stuff works, I could say this.velocity.setMag, which is set the magnitude to a random value between 0.5 and 1.5. Now, you can see now they're all moving with a slightly different velocity. This is by the way, a nice little almost like explosion motif and I could have them with a real big burst then they slow down. There's all sorts of physics-y stuff I could do with this but I just want to do flocking. How, how, how do I do flocking? Let's return to this paper here. This is the key behind Craig Reynolds' flocking algorithm. Three rules. Separation: steer to avoid crowding local flockmates. Alignment: steer towards the average heading of local flockmates. Cohesion: steer to move toward the average position of local flockmates. Something so crucial in that description is the word local, local. Local, what does that mean? Let's just do, I think the easiest one to do actually is alignment. Let's say there are five Boids. Actually, let's make a lot more of them. These are all of my Boids. This is the one that I'm currently operating around. It has a velocity vector like this. Let me just make up, let's just pretend like the ones around it are kind of moving all in the same direction. Because that'll be easier to think about. This is the current one that I'm looking at. And maybe some of these others are also moving in other directions also. I could take the approach to say this particular Boid needs to align itself to everything. But this is not really how complex systems in nature actually work. The emergent phenomena comes from this Boid having a limited perception of its environment. So maybe, it's actually only able to see things that are in front of it or behind it or to the right or what's probably the most simplest thing to implement is within some radius. So I only want to look at the Boids that are within this radius, meaning this one, this one, this one, this one, this one. What if I iterate over all these ones that are within some distance, average all of their velocities and shift this velocity in the direction of all those velocities. That's what we want to do. I am going to write a function now. I'm going to put it in the Boid and I'm going to call it align and it's going to get presumably an array of other Boids. The idea is this function align this Boid with all the other Boids. So what do I need to do? I'm going to make a variable called average which is going to be a vector then I am going to iterate over and I could do all of these with reduce probably in some higher order array functions. That's a great thing for you to do. (pop music) Refactor it later but I'm just going to do it this way, I'm actually going to use i because that's maybe going to help me. I'm not really sure but I'm going to use i right now. Actually, no, I'll still use a for-of loop. Let other, I'm going to call it other of Boids and I'm going to just say average.add other.velocity. So I'm adding up all of the velocities. This is how you do an average, right? Vectors, again you might want to go watch my videos about how vectors work, but a vector, a p5 vector just holds an x and a y or a x and y and a z. So I want to average the vector, represented as an arrow with an x component and a y component. If I want to average, just like I might average an array of numbers, I add them all up and divide by the total. Then I would say average.divide by Boids.length. This would be a way of getting the average velocity of all the Boids but remember I only want the ones within some distance. So actually, let's implement that right now. Let's have some sort of distance like max. I'll call it perception equals let's just make it arbitrarily like 100 and then I'm going to now say if the distance between this.position.x. You know what? I think there's a p5. This is fine. It's just going to be, it's kind of long the way I'm writing this but I don't mind. Let's actually make it in a variable. D = the distance between this Boid's position and the other Boid's position, x and y. If that distance is less than 100, look at that. Look at this fancy thing that it's doing. I have some prettier stuff that doesn't want to let me write a long line of code. I guess I'll keep that in there for right now. So I'm calculating the distance between this Boid's position and the other Boid's position. If that distance is less than 100, I'm adding it up and I should also probably have a total number, total = zero. Add it up. I also should probably ignore myself and if other does not equal this, right? So I basically, I might as well put that first. As long as the other thing is not me and the distance is less than 100, add it up and then divide by the total. But obviously, I only want to divide by the total if total is greater than zero. All right, so this is kind of a little bit of a long-winded algorithm now. And perception. If d is less than perception. So I'm starting with a perception, a radius of 100. Maybe that would be better if I called it perceptionRadius, that's really what it is. Starting with a perception radius of 100, an empty vector, adding up the velocities of any vector, any Boid that's near me, dividing by the total and as long as I've found one, dividing by that total. Now here's the thing. I could just say, for example I could do something weird, this will be really weird. I could just say this.velocity equals average. What if I did that? Then I said Boid, like here I said boid.align flock. I don't know what's going to happen here, but let's refresh this. They just basically don't go anywhere. Because they're all instantly equal to each other's velocity. That's not really a good way. What I need to do now is if I don't want to actually assign its velocity to that average directly. I want to steer towards it. This is where Craig Reynolds' steering formula comes in. A steering force equals some desired velocity minus the current actual velocity. It's kind of like the error. If I am moving this way. Sorry, if I'm moving this way but I want to move this way, the way I want to move minus the way I am moving is the steering force meaning push me in that direction. Apply a force. Maybe I'm going to turn the steering wheel. You can think of these as bees or cars or birds or whatever. This formula desired minus velocity will give me the steering force and what is the desired velocity here? The desired velocity is actually that average. I'm actually going to rename this to desired and I'm going to add it up and divide by total and then I'm going to say this.velocity equals, no, I'm not going to say that. I'm going to say steering equals. I should actually call this steering because I could do all of this. I don't need to save anything as I'm going. I'm going to call that the steering force. I'm going to add all the velocities. I'm going to divide by the total and then, I'm going to say steering force is the desired subtract this.velocity. And I'm going to say return steering. I want this function to basically, I want this function to return that force. So really what I should be doing is I should have, I'm going to write a function called flock. That's maybe more like, yeah. Flock with some number of Boids and then I'm going to say alignment equals align with those Boids. So I'm going to get this force and I suppose steering is here, so you know what? I should always return steering. I want to always return a vector but if it didn't find anything, I'll just return the zero vector. Then, what I'm going to do is I'm going to say acceleration add alignment. The idea, the way that a physics engine works, and this has to do with, this is a very simple crude physics engine. Force equals mass times acceleration. This is Newton's Law of Motion. Or acceleration equals force divided by mass or in a world where the mass of everything is one, acceleration equals force. So if I want to apply a force to this object, I just need to set the acceleration to that force. Actually, that's what I'm going to do just starting out here. I'm going to say acceleration equals alignment. So now, if I go back to sketch and I say Boid.flock Boids, we should now have all of Boids doing that with just implemented with that alignment rule. Let's see. Boids is not defined. Sketch.js line 14. Oh, it's called flock. Oh, this is my variable naming is terrible. But I'm going to leave it that way right way. Align is not defined. Boid.js align Boids. Oh, this .align, this .align Boids. Acceleration is not defined, oh my goodness. (pop music) Let me actually not have them all start in the same place because that is just to see this effect happen, let me actually start them in random places on the screen. Let me change that. Let me make the perception radius a little bit smaller and I don't know why this matters, but let me draw them like a little bit smaller. Let me actually have them start out going quite a bit faster. You can see as they get near each other, they start to get each other's velocity. They start to average each velocity with their neighbor's. You start to see these clumps moving together. So much more to do on this. Oh my goodness, but first a couple things. One is I have now basically allowed these Boids to steer with infinite power in a way. We should probably have a variable. I'm going to call it maxForce. I'm going to set it equal to one for a second and then what I'm going to do is here, I'm going to say a steering limit this.maxForce. What this does is it limits the magnitude, the length of that vector to some maximum force so this should be exactly the same. We're not seeing anything different but if I were to make maximum force 0.01, like really small right now, you're going to see they're not actually changing their velocity. They are, but very, very slowly. The other thing we're really going to need to do is I just have to give up and do this. I'm going to add something for the edges here. I'm going to say if this.position.x is greater than the width, this.position.x should equal zero. Else, if this.position.x is less than zero, this.position.x equals width. Then I'm just going to do that for the y, with, with, x, x, y, y, y, x, x, x, x, with, with. All right, and now here, let's do boid.edges. We should see them reappear wrapping around and now, let's go back to the Boid and let's make this 0.2 is kind of reasonable-ish. Interestingly enough, they're kind of like it slows them down which is interesting. But I'm only doing alignment right now, but you can see how this works. You can see how they're all starting to align with each other. I could keep them going at some minimum velocity which might make sense. You can see how these are going back and forth. But you get the idea. Why are they all slowing down? Is that just a coincidence? One thing I could do is this is actually going to be worth it because I should also give them a maximum speed. This is going to be a parameter of a variable that's going to allow me to control the system pretty well and I could consider their desired velocity in the alignment to not actually be the actual average velocity but just the average direction because I could then say steering.setMag to this.maximum speed. So basically I'm saying I always want to go in the direction of my neighbor but at maximum speed. I don't know that that's really an important detail, but if I add that in here, we might get the effect that I was hoping to get. Now, this is what I was expecting to see. As they get near each other, they're all starting to align together. Let me refresh that one more time. You can see that they're clumping and as they group, they all start to align. This is the alignment rule. This is a very simple rule. It's predictable, it's obvious, this is alignment. Now, all we need to do is add cohesion and separation. Separation is going to be the hardest one to do. So let's do cohesion next. Same thing, we're going to look at our neighbors. We should go back to what the rule was actually defined as, let's go back to Craig Reynolds' original page. Cohesion: steer to move toward the average position of local flockmates. This is actually not going to be that hard because we've already calculated the average velocity of local flockmates. Now, let's just duplicate that code. I know, I know we could refactor it. I won't play the song. Because we could probably do this all in one fell swoop. There's so many possibilities but now I just want to get the average location of my local flockmates and steer towards that. So, the way I'm going to do that is I'm just going to go nuts and copy paste this whole thing and call it cohesion and I'm going to keep the perception radius, I'm going to keep this idea of steering, keep this idea of total. Go through all the Boids, check the distance between myself and the other ones, as long as I'm not myself and within the perception radius, what am I doing? Steering not the others' velocity but the others' position. This should clearly be refactored into its own function but whatever. Then as long as I have at least one, I'm going to divide by the total. Now I don't want to set the magnitude to the maximum speed so what I have now in steering is the average location. So if this is the average location, let's say it's over here, then what I need is a vector that, that's clearly not the average location. But let's just say it was. The average location is kind of like where this Boid is. But I want to steer in that direction so to get a vector in that direction, I take the average location minus the current position of me which is basically saying now what I want to do is say steering subtract this.position so subtract my position now, I've got a vector that's pointing from me to the average location, remember which is in the steering variable then let's say I want to go at maximum speed. Then, this is now my desired velocity. I'm going to subtract out my current velocity, limit it to max force and there we go. That's cohesion. (bell rings) Now, in flock, let cohesion equal this.cohesion Boids and then oh, we have a big problem. This.acceleration equals alignment. This.acceleration equals cohesion. So how can I set the acceleration to two different forces? But before I even answer that, there's an easy answer to that question. Let's just comment out alignment and let's watch cohesion happen. We can see they start to group together. This is cohesion happening. And that neighbor radius, by the way, is a super important value. Right? That neighbor radius, if I were to change that and I have a different, that perception radius, if I were to change that to 10,000 they're all going to come together as one. Because they all see everybody. If I were to change that to 10, there's really little pairs. You can see they kind of get in groups of two. The force though isn't so strong. So if I were to change the maximum force to one, now you see them, almost like these little electron thingies that start, magnetic things that start to spin around each other and then fan off. There's so many possibilities here. I could make the maximum speed two. I'm not actually limiting it, you can see. There you go, that's what I was looking for. They get into these little groups but let me go back and say maximum speed is four. Maximum force is 22 and I realize this maximum speed is not actually a maximum speed because in update if I really wanted it to be a maximum speed, I would want to say this.velocity limit this.maxSpeed. Let's actually add that in and see what we get. Oh, and let's put the perception radius back at 100 and there we go. Now we have cohesion. We have cohesion, what happens if we have cohesion and alignment? How do we get cohesion and alignment? Okay, this is fun. This is really working. This is going to be a really long video. All right, cohesion and alignment. The problem rests here. This is actually a really easy thing to solve because this is called force, the answer here is force accumulation. In physics, if two forces are acting on an object, the resulting acceleration is the sum of those forces. So all I need to do is say acceleration.add alignment and acceleration.add cohesion because why is this not falling? Because the force of gravity is pointed this way and the other force of my hand holding it up is pointed in the other direction with an equal magnitude. Therefore, it is at rest. Those two forces added together have a net acceleration of zero. But obviously, all you do is add them together but there is this, if I actually do this right now, we're going to see some really crazy behavior. Looks like it's kind of working but it really isn't because what it should also have is this acceleration shouldn't accumulate over time. Every moment in time, I start with a net acceleration of zero and add all the forces together. So what I should do right before I flock is say this.acceleration set zero, zero. This is setting its values to zero and zero. Another way I could do that is just multiplying it by zero, because multiplying a vector by zero takes everything out. It could make a new vector, whatever. But this will work nicely and technically, this also might make more sense here because it's kind of like after I finish updating the velocity then I can reset the acceleration in case there's other things at play. Now we can see both cohesion and alignment are at play, look at this. Eventually, they're just all going to become this one clump. Come on, you can catch up, you got it, go go! Go, catch up! Yeah! One clump. One thing I would like to do. I really want to attach sliders to those parameters but I got to resist because we got to have separation. We've got alignment. (bell rings) And cohesion. (bell rings) Separation, this one is a little bit hard. Steer to avoid crowding local flockmates. Here's the me. That's me, here's my local flockmate. If this local flockmate is too close, it's within some sort of distance threshold, I want to steer to avoid that. What would be my desired velocity? My desired velocity would be to move in the opposite direction. So the idea is my desired velocity is in the observable. What if there is one here? My desired velocity would also be opposite direction so then my next desired velocity would be the average of these two. It even would make sense for the magnitude of that desired velocity to be proportional to the distance. So if this one is here, I maybe want to avoid it but I only really need to think about avoiding it a little bit. If this one's really close, I need to avoid it a lot. So the magnitude of the vector is inversely proportional to the distance of the local flockmate. This might end up with kind of like a desired velocity of something like this if I'm averaging those two together. So let's see if we can implement that. Not going to be super easy but let's do it. The nice thing is I'm going to start with cohesion because we've kind of, cohesion is close to separation. Again, boy. (pop music) ♪ I will refactor this later ♪ ♪ You know I will refactor this later ♪ ♪ I will refactor this later ♪ - I should show you that I'm wearing my I will factor this later t-shirt, by the way. Okay, so separation. I'm going to leave this at 100. Steering, I think I might need to use, I like the idea of just having one vector that ends up as, it's sort of the average here then it's the difference but I think I need to think, one step at a time. I'm creating this vector. This is the same. If it's not me and it's within the perception radius, what do I need? I need a vector, I'm going to call it difference which is the other's. I want a vector, sorry, that points from the other to me. Because I want that to go to the opposite direction. So it's my position minus the other. I want a vector, the new vector which is the subtraction between my position and the other's position. This is a way of calling the subtract function which doesn't operate on a vector but subtracts two vectors and gives us a new vector which is difference. Then, I want the difference to be inversely proportional to the distance so I'm going to multiply it or set its magnitude something like that. Or divide by distance. I could just say divide by distance, right? It's inversely proportional, the farther away it is, the lower the magnitude. The closer, the higher the magnitude. The distance, I guess technically that distance could be zero and that would be problematic but with floating point math rule, it's never going to get a value of zero. Then I want to add. Then that is the thing I add up. That's the thing I want to average. All those vectors that are pointed away from the things near me, I think we're actually in pretty good shape here. This isn't as hard as I thought. This was the tricky part here. Get a vector that's pointing away from the local flockmate, make it inversely proportional to the distance, add it up, then this is the same. Because, oh no. I don't need to subtract position. I kind of don't know if I want to. Let's leave this whole always go at maximum speed thing and then the rest is the same. Once I have that, just average it, set it to max speed, subtract out the velocity and limit it to maximum force and that's the end. Okay, just to be sure this is right, let's go into Boid. Let's go to flock. Let's add let separation equal this.separation and then let's add in separation. I want to not bother adding cohesion and alignment. Are these separating? Yeah, it looks like they are. It looks like when they get close to each other, they're kind of moving away from each other. Weird, weird behavior. This doesn't look like exactly what I expected. Let's give the maximum force, like really let them be super strong about separating. There we go. So that's more like what I expected. That's way too strong. Yeah. Why do they feel like they're, something is weird here. Let's give it a much smaller perception radius. Oh yeah, this is what I expected to see and I think it wasn't really a bug in my code. It's sort of a bug in my conceptual sense of how this works. This is pretty good, this is what I want to see. This is separation only. Now, we should be able to see and let's I'm going to do this really quickly. I'm going to not be thoughtful at all about the interface but just so I can debug this effectively, I am going to go into sketch.js. I am going to say let alignSlider, cohesionSlider and separationSlider. AlignSlider equals createSlider. I'm going to give it a range between one and five. Actually, let's give it a range between zero, sorry, zero and five, with a starting value of one and increment value of 0.1. I want to do this for all of these sliders. Then in Boid, I want to say where I call in flock, I want to just scale them. I'm going to say separation.multiply separationSlider.value. I'm going to do this also for cohesion and for alignment. Now, by the way. (bell rings) (train whistle blows) Flocking. This is now the flocking simulation. I didn't draw them as triangles. They're not rotating, but this is the flocking simulation. But just for the sake of argument, this should be what order did I make them in? They're not labeled. So let's just turn all the way up to separation. Oh boy. Let's turn off. This is only separation. Now, turn that off. Let's turn on only alignment. Whoa, that's oh they're just the alignment is so strong. When the alignment is so strong, it makes them go in circles around each other. I have to talk about why. Why did that happen that they were twirling in circles? Well, look at this. I'm updating them one at a time. The next one is flocking with the previous ones updated value, right? Instead of taking a snapshot of all their current velocities and then each one updates based on that snapshot, I start with a set of velocities. I update the first one based on the set of velocities and now its new velocity is there. So when the next one, it's actually updating itself based on the previous one that I updated which is not how the world works. It's not how time works. That's causing them to ripple into each other based on the order and go in circles. That is something that you might want to consider fixing. I'm not going to fix that but now I can really play with these, I can get different sort of qualities of flocking based on how strong I make these rules. So no alignment, a lot of cohesion. No separation, add a lot of separation. Less cohesion. Separation, I shouldn't make too strong but it's important and then let's add some alignment back in and there we go. Flocking. (bell rings) Okay, this coding challenge is complete. It is done. I will be uploading the code, if you want you can look at the video description to find the code as a snapshot, that's exactly what it is. Let's go make a list. Let's make a list on the board of things you might try to do. Number one, snapshot of all velocities. That's going to be my code for that. Number two, an optimization you can do is called spatial subdivision or a quadtree optimization. One of the things that makes this really slow. And I'll just show you here for a second, is if I were to try to do this with a 1,000 Boids. Look how slow this is. But you have to realize, it's not a big deal for p5 to draw 1,000 things moving. Why it's fine drawing 1,000 things moving but as soon as I put the flock function in, it's super slow. The reason that is is because it's got to do and every Boid, check every Boid against every other Boid check. So there's a way of subdividing the space, spatial subdivision into bins or buckets and have the Boids only check ones that are near each other in the same buckets. That's called, I probably have a lot of distance calculation, I could reduce the number of times I call it. But for this, quadtree or just simple spatial subdivision. These are things you could try. These are just code refactoring, really. Actually, the other thing you could try is really build a more sophisticated interface and there's lots of other parameters that you could control and try. There's the perception radius, there's maximum force, there's maximum speed. Each rule could have a different perception radius. So many possibilities. This is the stuff that you could do to really control and tweak all the values and play with it. Another thing you could do is just design, visual design, like are they triangles, circles, are they flapping butterflies. How do you, design of the Boids? Make your own beautiful visualization of how you design and draw them. Tadpole-like, there's so many ideas there. Another thing you could try to do is 3D. Can you extrapolate this into 3D? If you see the word quaternian anywhere you start to research, you might want to turn back. But you could extrapolate this into 3D. Another suggestion which came from the chat, thank you, is Boids with different parameters. There's no reason why every Boid has to have the same max speed or maximum force. Or same perception radius. You could implement this thing called the view rule. There's two things about this one. Each Boid can see everything around it. But what if the Boid itself could actually only see if it's moving in this direction, things that are within its particular view in front of it. Maybe it doesn't deal with anything that's behind it. It doesn't try to do cohesion or alignment or separation. Then the view rule which is posited by Flake in the book A Computational Beauty of Nature, I'll link to that book also. It's a wonderful book with tons, probably like 90% of the things on my channel are all from the Computational Beauty of Nature book. But Flake posits this additional rule that you always want to keep your view empty. So if there is a Boid in front of you, you want to steer that way to keep your view empty, clear and this might result, the theory is that this will result in something that looks more like that pattern that you'll see of actual birds flocking where they kind of appear almost in this triangular pattern. What's interesting about this, you see a pattern like this, you think, "Ah ha." This is a top-down behavior. There is a leader, this bird, who is saying to all the other birds, "Fan out from me and follow me." But yet, this type of intelligent behavior emergent phenomena can come actually only from simple, local rules of interaction. This by the way, this is what this whole video was about, if you didn't gather that already. Other ideas might be adding obstacles, other forces, there's thinking about these living in a world where they're interacting with other things, maybe there's a predator that comes in and is chasing them. All sorts of unique and interesting possibilities. So thank you everyone for watching this coding challenge. Put your other ideas for things people could do in the comments and then if you make your own version of this flocking code, please go to the codingtrain.com challenge page and submit a link to what you made. I will also make a version of this that runs in the web editor and link to all those resources like the Craig Reynolds original paper about flocking, the video on YouTube and the chapter in my Nature of Code book, okay? Thanks for watching this coding challenge on flocking and I'll see you soon. (train whistle blows) (upbeat music)
B1 中級 編碼挑戰#124:蜂群模擬。 (Coding Challenge #124: Flocking Simulation) 3 0 林宜悉 發佈於 2021 年 01 月 14 日 更多分享 分享 收藏 回報 影片單字