Notion - Update docs

This commit is contained in:
shiffman 2024-02-24 22:35:02 +00:00 committed by GitHub
parent 2fcf397f69
commit 219bb4dce5
4 changed files with 17 additions and 34 deletions

View file

@ -887,7 +887,7 @@ Composite.add(engine.world, mouseConstraint);</pre>
<figcaption></figcaption>
</figure>
</div>
<p>In this example, youll see that the <code>stiffness</code> property of the constraint is set to <code>0.7</code>, giving a bit of elasticity to the imaginary mouse string. Other properties such as <code>angularStiffness</code> and <code>damping</code> can also influence the mouses interaction. Play around with these values. What happens if you adjust the stiffness?</p>
<p>In this example, youll see that the <code>stiffness</code> property of the constraint is set to <code>0.7</code>, giving a bit of elasticity to the imaginary mouse string. Other properties such as <code>angularStiffness</code> and <code>damping</code> can also influence the mouses interaction. What happens if you adjust these values?</p>
<h2 id="adding-more-forces">Adding More Forces</h2>
<p>In <a href="/forces#">Chapter 2</a>, I covered how to build an environment with multiple forces at play. An object might respond to gravitational attraction, wind, air resistance, and so on. Clearly, forces are at work in Matter.js as rectangles and circles spin and fly around the screen! But so far, Ive demonstrated how to manipulate only a single global force: gravity.</p>
<pre class="codesplit" data-code-language="javascript"> let engine = Engine.create();
@ -897,7 +897,7 @@ Composite.add(engine.world, mouseConstraint);</pre>
<p>If I want to use any of the <a href="/forces#">Chapter 2</a> techniques with Matter.js, I need look no further than the trusty <code>applyForce()</code> method. In <a href="/forces#">Chapter 2</a>, I wrote this method as part of the <code>Mover</code> class. It received a vector, divided it by mass, and accumulated it into the movers acceleration. With Matter.js, the same method exists, so I no longer need to write all the details myself! I can call it with the static <code>Body.applyForce()</code>. Heres what that looks like in whats now the <code>Box</code> class:</p>
<pre class="codesplit" data-code-language="javascript">class Box {
applyForce(force) {
//{!1} Call <code>Body</code>s <code>applyForce()</code> method.
//{!1} Call <code>Body</code>s <code>applyForce()</code>.
Body.applyForce(this.body, this.body.position, force);
}
}</pre>

View file

@ -175,22 +175,19 @@ for (let i = 0; i &#x3C; cells.length; i++) {
<p>This pseudocode may suggest writing code like this:</p>
<pre class="codesplit" data-code-language="javascript">// For every cell in the array . . .
for (let i = 0; i &#x3C; cells.length; i++) {
//{!3} . . . take a look at the neighborhood.
let left = cells[i - 1];
let middle = cells[i];
let right = cells[i + 1];
//{!1} Look up the new value according to the rules.
let newstate = rules(left, middle, right);
//{!1} Set the cells state to the new value.
cells[i] = newstate;
}</pre>
<p>Im fairly close to getting this right but have a few issues to resolve. For one, Im farming out the calculation of a new state value to a function called <code>rules()</code>. Obviously, Im going to have to write this function, so my work isnt done, but what Im aiming for here is modularity. I want a <code>for</code> loop that provides a basic framework for managing any CA, regardless of the specific ruleset. If I want to try different rulesets, I shouldnt have to touch that framework at all; I can just rewrite the <code>rules()</code> function to compute the new states differently.</p>
<p>So I still have the <code>rules()</code> function to write, but more important, Ive made one minor blunder and one major blunder in the <code>for</code> loop. Lets examine the code more closely.</p>
<p>First, notice how easy it is to look at a cells neighbors. Because an array is an ordered list of data, I can use the numbering of the indices to know which cells are next to which cells. I know that cell number 15, for example, has cell 14 to its left and 16 to its right. More generally, I can say that for any cell <code>i</code>, its neighbors are <code>i - 1</code> and <code>i + 1</code>.</p>
<p>In fact, its not <em>quite</em> that easy. What have I done wrong? Think about how the code will execute. The first time through the loop, cell index <code>i</code> equals <code>0</code>. The code wants to look at cell 0s neighbors. Left is <code>i - 1</code> or <code>-1</code>. Oops! An array by definition doesnt have an element with an index of <code>-1</code>. It starts with <code>0</code>.</p>
<p>In fact, its not <em>quite</em> that easy. What have I done wrong? Think about how the code will execute. The first time through the loop, cell index <code>i</code> equals <code>0</code>. The code wants to look at cell 0s neighbors. Left is <code>i - 1</code> or <code>-1</code>. Oops! An array by definition doesnt have an element with an index of <code>-1</code>. It starts with index <code>0</code>!</p>
<p>I alluded to this problem of edge cases earlier in the chapter and said I could worry about it later. Well, later is now. How should I handle the cell on the edge that doesnt have a neighbor to both its left and its right? Here are three possible solutions to this problem:</p>
<ol>
<li><strong>Edges remain constant.</strong> This is perhaps the simplest solution. Dont bother to evaluate the edges, and always leave their state value constant (0 or 1).</li>
@ -210,16 +207,14 @@ for (let i = 1; i &#x3C; cells.length - 1; i++) {
<pre class="codesplit" data-code-language="javascript"> cells[i] = newstate;</pre>
<p>This may seem perfectly innocent. After all, once Ive computed a new state value, I want to assign the cell its new state. But think about the next iteration of the <code>for</code> loop. Lets say the new state for cell 5 was just computed, and the loop is moving on to cell 6. What happens next?</p>
<ul>
<li>Cell 6, generation 0 = a state, 0 or 1</li>
<li>Cell 6, generation 1 = a function of states for <strong>cell 5</strong>, cell 6, and cell 7 at <strong>generation 0</strong></li>
</ul>
<p>A cells new state is a function of the previous neighbor states, so in this case, the value of cell 5 at generation 0 is needed in order to calculate cell 6s new state at generation 1. Have I saved cell 5s value at generation 0? No, I have not. Remember, this line of code was just executed when <code>i</code> equaled<em> </em><code>5</code>:</p>
<p>A cells new state is a function of the previous neighbor states, so in this case, the value of cell 5 at generation 0 is needed in order to calculate cell 6s new state at generation 1. Have I saved cell 5s value at generation 0? No! Remember, this line of code was just executed for <code>i</code> equals<em> </em><code>5</code>:</p>
<pre class="codesplit" data-code-language="javascript"> cells[i] = newstate;</pre>
<p>Once this happens, cell 5s state at generation 0 is gone; <code>cells[5]</code> is now storing the value for generation 1. I cant overwrite the values in the array while Im processing the array, because I need those values to calculate the new values!</p>
<p>A solution to this problem is to have two arrays, one to store the current generations states and one for the next generations states. To save myself the step of reinitializing an array, Ill use JavaScripts <code>slice()</code> array method, which makes a copy of an array:</p>
<pre class="codesplit" data-code-language="javascript">//{!1 .bold} Create another array to store the states for the next generation.
let newcells = cells.slice();
for (let i = 1; i &#x3C; cells.length - 1; i++) {
//{!3} Look at the states from the current array.
let left = cells[i - 1];
@ -260,21 +255,17 @@ cells = newcells;</pre>
<pre class="codesplit" data-code-language="javascript"> function rules(a, b, c) {
// A quick way to concatenate three numbers into a string
let s = "" + a + b + c;
// The 2 in the second argument indicates that the number should be parsed as binary (base 2).
//{!1} The 2 in the second argument indicates that the number should be parsed as binary (base 2).
let index = parseInt(s, 2);
return ruleset[index];
}</pre>
<p>This solution has one tiny problem, however. Consider rule 222:</p>
<pre class="codesplit" data-code-language="javascript">// Rule 222
let ruleset = [1, 1, 0, 1, 1, 1, 1, 0];</pre>
<pre class="codesplit" data-code-language="javascript">let ruleset = [1, 1, 0, 1, 1, 1, 1, 0];</pre>
<p>And say the neighborhood being tested is 111. The resulting state should be equal to ruleset index 0, based on the way I first wrote the <code>rules()</code> function:</p>
<pre class="codesplit" data-code-language="javascript"> if (a === 1 &#x26;&#x26; b === 1 &#x26;&#x26; c === 1) return ruleset[0];</pre>
<p>The binary number 111 converts to the decimal number 7. But I dont want <code>ruleset[7]</code>; I want <code>ruleset[0]</code>. For this to work, I need to invert the index before looking up the state in the <code>ruleset</code> array:</p>
<pre class="codesplit" data-code-language="javascript"> // Invert the index so 0 becomes 7, 1 becomes 6, and so on.
return ruleset[7 - index];
</pre>
return ruleset[7 - index];</pre>
<p>I now have everything needed to compute the generations for a Wolfram elementary CA. Heres how the code looks all together:</p>
<pre class="codesplit" data-code-language="javascript">//{!1} Array for the cells
let cells = [];
@ -441,15 +432,11 @@ function rules(a, b, c) {
<p>The Game of Life has become something of a computational cliché, as myriad projects display the game on LEDs, screens, projection surfaces, and so on. But practicing building the system with code is still valuable for a few reasons.</p>
<p>For one, the Game of Life provides a good opportunity to practice skills with 2D arrays, nested loops, and more. Perhaps more important, however, this CAs core principles are tied directly to a core goal of this book: simulating the natural world with code. The Game of Life algorithm and technical implementation will provide you with the inspiration and foundation to build simulations that exhibit the characteristics and behaviors of biological systems of reproduction.</p>
<p>Unlike von Neumann, who created an extraordinarily complex system of states and rules, Conway wanted to achieve a similar lifelike result with the simplest set of rules possible. Gardner outlined Conways goals as follows:</p>
<blockquote data-type="epigraph">
<p>1. There should be no initial pattern for which there is a simple proof that the population can grow without limit.</p>
</blockquote>
<blockquote data-type="epigraph">
<p>2. There should be initial patterns that apparently do grow without limit.</p>
</blockquote>
<blockquote data-type="epigraph">
<p>3. There should be simple initial patterns that grow and change for a considerable period of time before coming to an end in three possible ways: fading away completely (from overcrowding or becoming too sparse), settling into a stable configuration that remains unchanged thereafter, or entering an oscillating phase in which they repeat an endless cycle of two or more periods.</p>
</blockquote>
<ol>
<li><em>There should be no initial pattern for which there is a simple proof that the population can grow without limit.</em></li>
<li><em>There should be initial patterns that apparently do grow without limit.</em></li>
<li><em>There should be simple initial patterns that grow and change for a considerable period of time before coming to an end in three possible ways: fading away completely (from overcrowding or becoming too sparse), settling into a stable configuration that remains unchanged thereafter, or entering an oscillating phase in which they repeat an endless cycle of two or more periods.</em></li>
</ol>
<p>This might sound cryptic, but it essentially describes a Wolfram class 4 CA. The CA should be patterned but unpredictable over time, eventually settling into a uniform or oscillating state. In other words, though Conway didnt use this terminology, the Game of Life should have all the properties of a <em>complex system</em>.</p>
<h3 id="the-rules-of-the-game">The Rules of the Game</h3>
<p>Lets look at how the Game of Life works. It wont take up too much time or space, since I can build on everything from Wolframs elementary CA. First, instead of a line of cells, I now have a 2D matrix of cells. As with the elementary CA, the possible states are 0 or 1. In this case, however, since the system is all about life, 0 means “dead” and 1 means “alive.”</p>
@ -480,11 +467,11 @@ function rules(a, b, c) {
<figcaption>Figure 7.27: Example scenarios for death and birth in the Game of Life</figcaption>
</figure>
<p>With the elementary CA, I visualized many generations at once, stacked as rows in a 2D grid. With the Game of Life, however, the CA is in two dimensions. I could try to create an elaborate 3D visualization of the results and stack all the generations in a cube structure (and in fact, you might want to try this as an exercise), but a more typical way to visualize the Game of Life is to treat each generation as a single frame in an animation. This way, instead of viewing all the generations at once, you see them one at a time, and the result resembles rapidly developing bacteria in a petri dish.</p>
<p>One of the exciting aspects of the Game of Life is that some known initial patterns yield intriguing results. For example, the patterns shown in Figure 7.28 remain static and never change.</p>
<figure>
<img src="images/07_ca/07_ca_29.png" alt="Figure 7.28: Initial configurations of cells that remain stable">
<figcaption>Figure 7.28: Initial configurations of cells that remain stable</figcaption>
</figure>
<p>One of the exciting aspects of the Game of Life is that some known initial patterns yield intriguing results. For example, the patterns shown in Figure 7.28 remain static and never change.</p>
<p>The patterns in Figure 7.29 oscillate back and forth between two states.</p>
<figure>
<img src="images/07_ca/07_ca_30.png" alt="Figure 7.29: Initial configurations of cells that oscillate between two states">
@ -497,7 +484,7 @@ function rules(a, b, c) {
</figure>
<p>If youre interested in these patterns, several good out-of-the-box Game of Life online demonstrations allow you to configure the CAs initial state and watch it run at varying speeds. Here are two examples:</p>
<ul>
<li><a href="http://www.playfulinvention.com/emergence/">Exploring Emergence by Mitchel Resnick and Brian Silverman, Lifelong Kindergarten Group, MIT Media Laboratory</a></li>
<li><a href="https://www.playfulinvention.com/emergence/">Exploring Emergence by Mitchel Resnick and Brian Silverman, Lifelong Kindergarten Group, MIT Media Laboratory</a></li>
<li><a href="https://sklise.github.io/conways-game-of-life/">Conways Game of Life in p5.js by Steven Klise</a></li>
</ul>
<p>For the example Ill build in the next section, Ill focus on randomly initializing the states for each cell.</p>
@ -513,7 +500,7 @@ for (let i = 0; i &#x3C; columns; i++) {
<p>Ill begin by initializing each cell of the board with a random state, 0 or 1:</p>
<pre class="codesplit" data-code-language="javascript">for (let i = 0; i &#x3C; columns; i++) {
for (let j = 0; j &#x3C; rows; j++) {
//{!1} Initialize each cell with a 0 or 1.
//{!1} Start each cell with a 0 or 1.
board[i][j] = floor(random(2));
}
}</pre>
@ -544,23 +531,19 @@ for (let i = 0; i &#x3C; columns; i++) {
</figure>
<p>The Game of Life rules operate by knowing how many neighbors are alive. If I create a variable <code>neighborSum</code> and increment it for each neighbor with a state of 1, Ill have the total of live neighbors:</p>
<pre class="codesplit" data-code-language="javascript">let neighborSum = 0;
// Top row of neighbors
if (board[i - 1][j - 1] === 1) neighborSum++;
if (board[i ][j - 1] === 1) neighborSum++;
if (board[i + 1][j - 1] === 1) neighborSum++;
// Middle row of neighbors (note <code>i</code>, <code>j</code> is skipped)
if (board[i - 1][j ] === 1) neighborSum++;
if (board[i + 1][j ] === 1) neighborSum++;
// Bottom row of neighbors
if (board[i - 1][j + 1] === 1) neighborSum++;
if (board[i ][j + 1] === 1) neighborSum++;
if (board[i + 1][j + 1] === 1) neighborSum++;</pre>
<p>Just as with the Wolfram CA, I find myself writing out a bunch of <code>if</code> statements. This is another situation where, for teaching purposes, its useful and clear to write the code this way, explicitly stating every step (each time a neighbor has a state of 1, the counter increases). Nevertheless, its a bit silly to say, “If the cell state equals 1, add 1 to a counter” when I could instead just say, “Add every cell state to a counter.” After all, if the state can be only 0 or 1, the sum of all the neighbors states will yield the total number of live cells. Since the neighbors are arranged in a mini 3<span data-type="equation">\times</span>3 grid, I can introduce another nested loop to compute the sum more efficiently:</p>
<pre class="codesplit" data-code-language="javascript">let neighborSum = 0;
//{!2} Use <code>k</code> and <code>l</code> as the counters since <code>i</code> and <code>j</code> are already used!
for (let k = -1; k &#x3C;= 1; k++) {
for (let l = -1; l &#x3C;= 1; l++) {
@ -653,7 +636,6 @@ board = next;</pre>
constructor(state, x, y, w) {
// What is the cells state?
this.state = state;
// Position and size
this.x = x;
this.y = y;

View file

@ -7,7 +7,7 @@
<p>Every one of them a splinter in my eye</p>
<p>I hate the Peano Space and the Koch Curve</p>
<p>I fear the Cantor Ternary Set</p>
<p>The Sierpinski Gasket makes me wanna cry</p>
<p>The Sierpiński Gasket makes me wanna cry</p>
<p>And a million miles away a butterfly flapped its wings</p>
<p>On a cold November day a man named Benoit Mandelbrot was born</p>
<div class="chapter-opening-quote-source">

View file

@ -10,6 +10,7 @@ function setup() {
// Make the Engine
let engine = Engine.create();
engine.gravity.set(1, 0);
let render = Matter.Render.create({
canvas: canvas.elt,