字幕列表 影片播放 列印英文字幕 - I remember January 2019. Do you remember January 2019? Oh, it was a simpler time. Something happened that month. A video came out on the Internet. This video was called, The most unexpected answer, I'm looking it up, The most unexpected answer to a counting puzzle. And this video was from one of my favorite YouTube channels, 3Blue1Brown, where I learn a lot of wonderful math stuff and I get a lot of inspiration for stuff that I do. And what this video was about is it was about these two blocks. There was a small block, and a big block. And these blocks, they moved around, they bounced into each other, they clacked. It was like, the most beautiful clacking sound I've ever heard. And then they bounced into a wall, and after awhile, just count up how many times they're bouncing into each other, and this weird thing happens, this number appears, this number which is the digits of pi appears based on how many times they're clacking into each other, which is crazy. Now, if you want to know why that occurs, you should go and watch the 3Blue1Brown video series, and even towards the end of that three part series, 3Blue1Brown makes a connection to optics, and there's so much beauty and wonder in there. This is not a new concept, you can find some other resources. There's a wonderful paper from 2003, I'm looking up the title of it now, by G. Galperin called Playing Pool With Pi, The Number Pi from a Billiard Point of View. There's a Numberphile video, and I'll also link to some other resources where you can read about this particular phenomenon and why it occurs in this video subscription. But that's what not I do on this channel. What I do on this channel is I code stuff. So I am going to attempt in this coding challenge to make my own version of 3Blue1Brown's collision clacking magic wonderful thing, and we're going to see like, can I really make this happen, just in the browser and JavaScript, and can I actually write some code that's accurate enough to get the digits of pi? And where does this all break down, and what happens? So, let's code. (whistle blows) Now, to celebrate Pi Day, I'm going to do something a little bit different. I'm going to just start with a little bit of boilerplate code. I kind of use the same stuff in so many of my examples, maybe I can save you five minutes here of retyping this part out. So what I'm starting with is just a very basic object-oriented system. I have a class called Block, and each Block has an x and y, that's where it is in the canvas, and it has a width, it's a square, so the width and the height are the same. And then when I want to draw it, I just draw it as an image, and I've loaded one of my coding train characters. The square character from the coding train. I also have in here a little clack sound loaded that I may or may not use, and then, I have created just two blocks, block1 appears at 100 with a size of 20, block2 appears at 200 with a size of 150, and then I am showing both those blocks. Alright, so what do I need to do? What I want to first do is give these blocks a velocity. So let me go into the Block class and add a velocity, I'm going to call it v. And then I am also going to write a function called update where I will say this.x += v. So the idea here is it has a velocity, and x changes by that velocity. Then, I'll go back to the sketch, and let me give them a velocity, like the block1 is going to have a velocity of zero, and block2 will have a velocity of negative one, and then I should be able to say also here, I can just call update for both of them. Update, update. V is not defined, did I really forget the this dot, already, so already in this challenge? I think that's what I did, I totally forgot this dot. Oh, this dot this dot. So now what I need to do is I need to figure out, how do I check if the two blocks have knocked into each other? Okay, the way that I would do that, let's write a function, I like to do everything in the class when possible. I'm going to write a function called collide, and I'm checking if I'm colliding with another block, call it other. So I'm checking if this block is colliding with some other arbitrary block. So how do I know? If this is block1 with an x and a width, and this is block2, with an x and a width, I know that they are not intersecting if the left side of block2 is greater than the right side of block1, or if it was on the other side, right? If the left side of block1 was greater than the right side of block2. Otherwise, they must be intersecting each other. And I know this is a really simple situation, I don't need to check y's and heights, because they're only moving along this one-dimensional horizontal space. By the way, the math is going to be a key concept here, and where I'm getting to that, if you know, you're like, why not the math? Talk about the math! So now I can say, I can look and basically say, like, if this.x + w, that's the right-hand side, is greater than other.x, other.x, right? Oh no, is less than, right? Or, if this.x is greater than the other.x + other.w, and this has to be a this dot, alright? So now, I'm going to say, println, oh no, print, not collide, not collide, otherwise, print collide. Let's see if I got this right. So it's moving, it's moving, oh, I got to call this function, that would be nice. So I'm going to say block1, 1.collide block2. So I'm adding that in, and it's moving, not collide, not collide, not collide, not collide, not collide, not collide, it's going to collide. It's colliding, it's colliding, it's colliding! Eventually, it's going to get to the other side, go to the other side, get to the other side, not colliding anymore. Okay, my algorithm was correct. So, really if I want that, I want that to just return true or false. So what I'm going to do is, I'm actually going to say, I'm just going to, I could write return, write return false here, but another way I could do this is just say return not, not this expression. This is a test to see if those two blocks are colliding with each other. Oof, alright. If block1 collides with block2, now what I want to say is block1.bounce block2. Alright, I want them to bounce off of each other. So certainly at some point we're going to have to deal with the fact that there's a wall here. But right now I'm just looking at these two, and what I want to do is I want to calculate the new velocities from perfectly elastic collision. So this has a velocity, this presumably also has a velocity, maybe it's moving that way, maybe it's that way, maybe it's at rest. And, in an elastic collision, there's no friction, there's nothing else here in this environment. They're not like squishy things, no momentum, no energy, nothing is lost. So I need the formula for an elastic collision. Luckily, I have that open right over here, and then you can see this is a formula that's following both the conversation of momentum and the conservation of kinetic energy. So with both of these formulas from physics we can then get the new velocity, right? U here is the old velocity, and v is the new velocity. So the new velocity is a function of, the both object's mass, and their previous velocity. Let's first add the idea of mass. So I'm going to go in here, and I'm going to add the idea of mass. And first I'm just going to say, hey, the mass is one, so let's not worry about mass right now. By the way, what happens if the mass is one? Think about this. If they're equal mass. If they're both equal mass, and this one is stationary, and this one's moving, and they clack, right? All of the momentum and energy is transferred, this one stops, this one moves. But that's not what's going to happen when the masses of each are different, so that's when it's going to get really interesting, okay. So now we want a function called bounce, and I'm also going to say other. And it would be really nice if I could just have that formula right over there for me to refer to. Alright, so I took a screenshot of the formula, so I've got it over there to refer to, and I just need to implement it here in this bounce function. So, one is this, two is other. So I'm writing it, I'm not referring to my blocks as block1 and block2, I'm referring it to within the object as this and other. So what I want to do is say this.v, but I don't want to start messing with v, because the old value of v is part of the formula. So let's do this, const.v. Actually, you know what? Let's do, let's do, sorry, let newV = this.m-other.m, divided by what? Let's make a variable called sum of M, which is this.m + other.m. So this is divided by sum of M, and then, times this.v, right? So that's this side, and then, I mean, I could do this in one line, but I'm running out of space here. So then += 2 * other.m, divided by sumM * other.v. So I think what might make sense here is the thing is I don't want to update this object's velocity. I guess I could do both of them. I'll just do both of them in this function. I was thinking I could return it, and then 'cause it's the same formula both ways, I'd rather not duplicate my code. Let's try it this way. I'm going to return newV. You can see how this is the same formula twice, it's just written in reverse order for v2, but if I change the two there, and change all the twos to ones, we'll get the same thing. So I should be able to say, if I go back into sketch.js, and I say, I'm going to say v1 = block1.bounce block2. I'm going to say v2 = block2.bounce b1, block, block1. And then I should say, I'm going to update their velocities. This I don't know if I love, but this will work. So let's see what we got here. C'mon, clickety clack, go, bounce that thing! Look at that, they're the same mass. So, by the way, let's, it's like, moving, it's like a funeral dirge over here, the way that thing's moving. Let's give it a bit of a faster velocity to start. And we can see, boom, that's a perfect elastically, now, it doesn't look very realistic, because really what we're seeing here, is like, should look like this, right? That's what we want. If both of those objects have the same exact mass, one's going to transfer all of its energy momentum to the other one. Now what if they have different mass? So let's give this one a size of 20, and this one a size of 200, and actually, I'm also going to give them each individual masses. So this will have a mass of one, and this one's going to have a mass of 100. So not perfect, and some weird stuff is happening, because I'm introducing another argument here. So now let's see what happens. Oh, there we go, that's what we want! Look at this, right? Now the formula's working with different masses. Alright, we're doing well, oh, we need a wall, right? We need for the little block to hit the edge. So the wall is another thing that doesn't exist in the real world. We're looking at a perfectly elastic collision with a immovable, fixed static wall. In other words, a wall, a block of infinite mass. So we're going to give this wall as having infinite mass, but honestly, the easiest way to do this is just, if it hits the wall, just negate, reverse its velocity. That will simulate a perfectly elastic collision with an object of infinite mass. So I'm going to write a function like hitWall. If this.x is less than zero, then this.v *= -1. And then I only need to, in this case, I only ever need to check block1. So now I'm going to check block1.hitWall, and let's go here, boom, boom boom boom boom. Amazingly, that kind of sort of did something. Now, are we getting the right numbers? This is the question. And also, I kind of want to hear that clack. Don't you want to hear that clack? So let's add the clack. Oh, I just realized, this way that I did that, I really should have this also be if block hitWall, then do something like, block1, let's let these be separate functions, reverse, because, I also, I'm going to need to do counting, there's a bunch of things that are going to need to happen so it's good for me to actually do it this way. Return, I'm going to say reverse, and then, okay, so this looks right. Let me make sure this is the same thing. Whoops, ah, I hit refresh, sorry. Da-da-da-da-da-da-da! Alright, so that did something. Now, let's add the clack. I mean, we're really here just for the clack. The clack in the 3Blue1Brown video is the most beautiful clack ever. So I think if I said clack.play, let's see what happens. (block clacks) Ooh! Oh, that's kind of good! I need to clack when it hits the wall though, too. (block clacks) Oh, the clacking, oh! It's a little weird how I lack the clacking so much, is it? I don't know, it's very satisfying. Alright, so now let's count, I mean, in theory, we're done! (bell rings) I have a feeling we're going to run into some big problems. But let's say, int. So I'm going to start with a count of zero. I am going to create a dom element, I'll call it countDiv, because I'm going to make a countDiv = createDiv, with the count in it. Ah, ah, ah. And then, I am going to say, count, countDiv.html count, and we will increase the count every time something collides. Whoops, line six error. Oh no, int, what am I doing, int? Int, oh, I feel like I'm in Processing, it's a lot. Let's make them the same mass. Oh, and I got to make this bigger. Yes, okay, and then, let's number format this. So, okay. (block clacks) Hey, that's kind of like pi, three! Now, I'm going to make, okay, so here's the thing. The magic number here is 100. So, if the mass of one of the objects is one, the mass of block1 is one, the mass of block2 should be the mass of block1 times 100 to the power of something. So the one here, this doesn't matter, 'cause this is just one. The ratio, so I could do 100 to the first power, right? 100 to the zero power, which would be their equal, 100 to one, which would be one. 100 to the second power, which would make this 10,000, right, to the third power? You see where we're going here. So what I want to do is, that's how I'm going to calculate the mass. So, I guess what I want to say is, if the digits are zero, then is then two, I'm going to say const m2 equals power. 100, to the number of digits. (block clacks) So that's one digit. And so, and now, that's zero digits really, because, I'm confused when I'm talking about digits, right? What I mean by zero is the number of digits after the dot. Pi is 3.1, okay? So, now let's have one digit after the dot. (block clacks) 3.1, that's crazy! Alright, here we go, two digits. (block clacks) Uh. Wait, hold on a second. Actually, the way that this would make the most sense is for, if I want to get two digits, I should be saying 100 to the power of one, which is digits minus one, and then this makes sense down here. This is my digits plus one, and there we go, this is two digits, I should get the number 31, okay, that's good. And then if I change this to one digit, I get the number three. And by the way, when do I decide when to stop counting? So at a certain point, the second block, or I know which block is the first, which block is the second, is going to just go off and running forever, and they're not going to have another chance to ever collide. So I'm just doing that visually right now. But we could actually put something in here to determine when it's finished. But you could see here, now even though they're both moving in the direction that way, but the second, the larger block is moving faster, so they're never going to collide again, and that's something I could create a conditional to test for. But, let's talk about the real problem here. The real problem is Euler integration. The real problem is, the phony baloney physics simulation that I'm doing here. I am taking a giant step in time forward each frame of animation. And so the block is like, moving, then it's covering, it's colliding, again, everything is wildly inaccurate. So there are a variety of techniques of making a simulation more accurate. This diagram over here is illustrating this concept. So if the blue line is like the real thing that would happen in the real world with continuous time, if I'm just making guesses every so often, with the red line, with the Euler integration, just adding the velocity with large time steps, you could see how the thing gets out of place. And this, if you were programming a simulation to try to get a rocket to land on some, moon, planet thing, you're going to have a problem with Euler integration. So there are a variety of techniques that are different types of integration techniques for physics engines, and then of course, 3Blue1Brown talks about other ways to calculate the number of collisions that would happen based on thinking about the space in a different way. But what I'm going to do here is just see, what if I just make those time steps smaller? So in other words, if I recreate that diagram like this, and this is, you know, with large time steps, well, if I could get smaller time steps, I could stay closer to what it really would be. So a way of doing that might be the following: I am going to create a variable, I'm going to call it a timeSteps. And I'm going to put the digits at one, just so we know it's always going to work, and I'm going to say the timeSteps, I'm going to just try, let's add 10 extra timeSteps. We can make this a constant. So there's always going to be, every time through draw, I want to do this 10 times. So I'm going to say, for let i = 0, i less than the number of timeSteps, i++. And I'm just going to do this, all of this 10 times. All of this, by that I mean, check the collisions, bounce the blocks, play the sound. And let's comment out the sound for a second. We'll have to think of a different way of dealing with that. Update the blocks, I'm going to show once they're drawn. So if I'm doing that 10 times, the other thing I can do here is I can divide the velocity that I want to start with with the number of timeSteps. So it's actually, it's as if I'm doing the same thing, but with a tinier, tinier velocity, and just do it multiple times through draw, so our animation happens faster. Let's see if this allows us to do. Let's just check to make sure this is still working. One, two, three, oh, I miss the clack. I really miss the clacking. Let's try two digits. One, two, three, four, five, da, da, da, 31, alright, that's pretty good. Now let's try three digits. And let's see, we should get 314! C'mon, ah, ah, go back! Hit the wall again, hit the wall again, clack, 314, woo! Let's add the clack back in. What happens if I add the sound back in? (block clacks) Hah, that was tolerable. Alright, 314, okay. Let's try four digits. (block clacks) Uh oh, goodbye! So that didn't work, what if I we have 100 timeSteps? (block clacks) Okay, okay, that was, hey, it worked out! Alright, we got to deal with the sound. Alright, so I think, basically, what we should do with the sound is I'm just going to say, let clackSound, I'm just going to assume it's not clacking, and then if it clacks once, if it hits anything, I will say clackSound = true. Like, only if it collides, either with the wall, or the other block. And then, I'm guaranteeing now that I can only, I will only play the sound once every time through draw. So this should really help things. So let's try this now, 100 timeSteps, four digits. (block clacks) 3.141, we're doing really well here! Okay, now, five digits? How about 1,000 timeSteps? (block clacks) Looks pretty good! Six digits, 10,000 time steps? (block clacks) That's pi, right, oh, clack! Oh, 3.14159! Alright, save, I'm saving! Seven digits, 100,000? (block clacks) What, that seriously worked? That's pi, right? I got seven digits just through this method? I really didn't think I was going to get that high. Alright, let's try eight. Let's try, like, 500,000. (block clacks) What, is that, is there a six there in pi? I mean, could I really get one million? I'm surprised that it actually, like, will go to one million. Nine, but I don't think that it can handle 10 million. 10 million timeSteps each time through draw? Can I get away with, like, 5 million? (block clacks) What? 1.4, what's pi, 14159265? That's right, that's pi! Can I really do 10 million timeSteps? (block clacks) I didn't think JavaScript, oh yeah, the other thing I should do here, is I should constrain where it's being drawn. You can hear the frame rate is getting really, really low, so I'm just going to give these a variable called x constraint. I want to say block1 can't be drawn any further than zero, and block2, if this size is 20, can't be drawn any further than 20, and where I'm drawing it, let me say constant x equals constrain, this.x between this.xConstraint, and I don't know, the width of the window should be fine. And then I'm going to draw it at xConstraint. This will just visually, I think, make it look more correct. Oh, when an x constraint is not defined, 'cause I probably forgot the this dot. (laughs) Yes, I forgot the this dot. Oh, oops, no, no, I want to draw at the x that's constraint, I don't know what I'm doing here, put this here, and now let's watch. (block clacks) It's counting. A lot of collisions. Alright, can we, can we get to 10 digits? Let's try, like 500 bazillion. It's just running really slow. There's too many timeSteps. But I think we probably will get the right number. (cheerful mamba music) ♪ It's the Pi Song ♪ ♪ We're counting the collisions on the Pi Song ♪ ♪ We're counting the collisions on the Pi Song ♪ ♪ Pi Song, Pi Song, Pi Song ♪ (cheerful mamba music) I don't have the digits of pi memorized. Oh, hold on, ah! (block clacks) Ah, we got 10 digits everybody, whoohoo! Only 500 million, no, 50 million timeSteps, I had to look at this very close up. 50 million timeSteps per time per draw, I mean, why not, right? Oh, let's go to, this goes to 11, we have to go to 11. Let's just add another zero, because why not. Loading, loading, okay. Alright, so this, I might need to speed this up, but I'll at least play you a little song. (peaceful ukulele music) ♪ 3.14159 ♪ ♪ 2653589 ♪ ♪ 793238462 ♪ ♪ 2643 ♪ ♪ 832 ♪ ♪ 795 ♪ ♪ 884 ♪ ♪ 1971 ♪ ♪ 939 ♪ ♪ 375105 ♪ ♪ 1, woops, 8209 ♪ ♪ 7494 ♪ ♪ 45923 ♪ (block clacks) Ah, whoa! Oh, yes, oh, it's moving, it's moving! Okay, three, one, four, one, five, nine, two, six, five, three. Okay, it's got to hit one more time. Go, catch up little guy! Catch up little square, little one! You can do it, give me a clack! You can do it, you can do it, you can do it! Ah, yes! 31415926535, that's 11 digits of pi, in JavaScript, p5, in the p5 Web Editor, just by elastic collisions of two squares! Woo, what a coding challenge. (whistle blows) Alright, thank you for watching this coding challenge. This actually did happen during a live stream, every once in awhile I do live streams for YouTube members and patrons, and I just happened to be doing this during one of those, but this was so sort of fun and weird that I will include a link to that unlisted live stream if you want to take a look and see the long version of all of the time spent actually doing this coding challenge, and waiting and waiting and waiting. I also have made a Processing version of this. I've made some other attempts, some of them rather comical. For example, I tried to say, could box 2D actually do a good job of figuring out the elastic collisions more accurately? As you can see, that did not work. I also tried using big decimal. Now, in JavaScript, I'm using floating point numbers, and a floating point number just has 32 bits of memory to store, it's really just about seven digits, I think, you could, in Processing, I could use a double, which has 64 bits of memory to store the decimal number. But there are lots of implementations, BigDecimal being one in Java for storing huge, huge, decimal numbers with really high precision, and I believe there are some JavaScript analogs to that. So, I will include links to all of that stuff in the code that goes along with this. If you can come up with a creative way of visualizing this, of optimizing it to make it run, you should definitely check out 3Blue1Brown's video. I have a version where I implemented the optics method, but I had to load the number pi in order to then calculate. So this is a question I'm asking you, the audience. Is there a way to do, to count the number of collisions, either without tiny tiny timeSteps that take forever, or by having access to the number pi in the first place? I would love to know your answer or thoughts on that. Go to thecodingtrain.com. Link in the description to this challenge page, and you can submit your own version of it there. Thanks for watching, and I'll see you, uh, see you before next year on Pi Day, hopefully. I mean, there's like Tau Day, it's coming up! June is, Summer's right around the corner! June is bustin' out all over! Goodbye, see you later! (whistle blows) (upbeat music) (peaceful ukulele music) ♪ 3.1415 ♪ ♪ 926535 ♪ ♪ 89793238 ♪ ♪ 46, it's Pi Day, Pi Day ♪ ♪ Got to calculate from Pi Day ♪ ♪ Everyone's looking forward to the digits ♪ ♪ Digits, it's Pi Day, Pi Day ♪ ♪ Got to calculate on Pi Day ♪ ♪ Everyone's looking forward to the digits ♪ ♪ The digits ♪ ♪ Partying, partying, yeah ♪ ♪ Partying, party, yeah ♪ ♪ Fun, fun, fun, fun, Pi Day ♪ (upbeat music)
B1 中級 編碼挑戰#139:用碰撞計算圓周率的數字 (Coding Challenge #139: Calculating Digits of Pi with Collisions) 1 0 林宜悉 發佈於 2021 年 01 月 14 日 更多分享 分享 收藏 回報 影片單字