### Gravity wells

Given a few gravity wells at random positions, spawn particles and trace their paths if they live long enough without crashing into a well.

```// LL 2021

const turtle = new Turtle();

const gravity_wells = 2;     // min=2, max=10, step=1
const particles = 5000;      // min=1, max=10000, step=1
const opacity = 0.02;        // min=0.02, max=1, step=0.01
const gravity = 1000;        // min=1, max=10000, step=1
const seed = 0;              // min=0, max=1000, step=1
const iterations = 150;      // min=1, max=500, step=1
const scale = 1.02;          // min=0.1, max=10, step=0.1

var gravity_well_list = null;
var particle_count = 0;

const max_attempts = 1000000;

Canvas.setpenopacity(opacity);

var rng;

class vec2
{
constructor(x, y) { this.x = x; this.y = y; }
dup() { return new vec2(this.x, this.y); }
set(v2) { this.x = v2.x; this.y = v2.y; }
add(v2) { this.x += v2.x; this.y += v2.y; }
sub(v2) { this.x -= v2.x; this.y -= v2.y; }
length() { return Math.hypot(this.x, this.y); }
normalize() { var length = this.length(); if (length > 0) { this.x /= length; this.y /= length; } }
multiply_f(f) { this.x *= f; this.y *= f; }
}

class GravityWell {
constructor(x, y, r) {
this.position = new vec2(x, y);
}

draw() {
turtle.jump(this.position.x / scale, (this.position.y - this.radius) / scale);
}
}

class Particle {
constructor(x, y, prev_x, prev_y) {
this.current_position = new vec2(x, y);
this.previous_position = new vec2(prev_x, prev_y);
this.died = false;
this.history = [this.current_position.dup()];
}

update() {
var v = this.current_position.dup();
v.sub(this.previous_position);
this.previous_position.set(this.current_position);

gravity_well_list.forEach(w => {
var vgravity = this.current_position.dup();
vgravity.sub(w.position);
const distance = vgravity.length();
} else {
this.died = true;
}
});

const friction = 0.99;
v.multiply_f(friction);

this.history.push(this.current_position.dup());
}

draw() {
turtle.up();
this.history.forEach(pos => {
turtle.goto(pos.x / scale, pos.y / scale);
turtle.down();
});
}
}

function plateau(n, level) {
return Math.round(n/level)*level;
}

// The walk function will be called until it returns false.
function walk(i, t) {
if (i==0) {
rng = new RNG(seed);

gravity_well_list = [];
for (var j=0; j<gravity_wells; j++) {
const x = (rng.nextFloat() - 0.5) * (200-r);
const y = (rng.nextFloat() - 0.5) * (200-r);
gravity_well_list.push(new GravityWell(x, y, r));
}

particle_count = 0;
}

const chance = rng.nextFloat();
const x = 200 * (plateau(rng.nextFloat(), (chance < 0.5) ? 1 : 0.0625) - 0.5);
const y = 200 * (plateau(rng.nextFloat(), (chance > 0.5) ? 1 : 0.0625) - 0.5);
// const x = 200 * (plateau(rng.nextFloat(), 0.00025) - 0.5);
// const y = -100;
const vel = 0.25;
const prev_x = x + (rng.nextFloat() - 0.5) * vel;
const prev_y = y + (rng.nextFloat() - 0.5) * vel;
const particle = new Particle(x, y, prev_x, prev_y);

for (j=0; j<iterations*t && !particle.died; j++) {
particle.update();
}

if (!particle.died) {
particle.draw();
particle_count++;
}

//sleep(10);

return particle_count < particles && i < max_attempts;
}

////////////////////////
// Utils
////////////////////////

function sleep(milliseconds) { const start=Date.now(); while (Date.now()-start < milliseconds) {} }

///////////////

// Minified Random Number Generator from https://turtletoy.net/turtle/ab7a7e539e
function RNG(t){return new class{constructor(t){this.m=2147483648,this.a=1103515245,this.c=12345,this.state=t||Math.floor(Math.random()*(this.m-1))}nextFloat(){return this.state=(this.a*this.state+this.c)%this.m,this.state/(this.m-1)}}(t)}

```