float r = 10;
class Group {
int chips;
float x, y;
Group() {
chips = 1;
x = random(1);
y = random(1);
}
void display() {
if(hasChip()) {
float ax = x*width;
float ay = y*height;
// external "mask"
fill(255);
noStroke();
ellipse(ax,ay,chips+6,chips+6);
// actual pile
stroke(0);
ellipse(ax,ay,chips+2,chips+2);
if(chips >= 20) {
textFont(font, 14);
fill(200,0,0);
text(nf(chips,0,0),ax-8,ay+6);
}
}
}
boolean hasChip() {
return (chips > 0);
}
void remove() {chips--;}
void add() {chips++;}
}
float step = (float) 1/200; // length of a step
float skew = (float) 1/10; //randomness scaling
float thresh = (float) 1/40; // minimum distance
class Wanderer {
float lastx, lasty, x, y, rot, rota;
boolean carry;
int lastchip;
Wanderer() {
carry = false;
x = random(1);
y = random(1);
lastx = x;
lasty = y;
rot = random(TWO_PI) - PI;
rota = 0;
}
void update() {
move();
lookAround();
}
void move() {
rot += rota * skew;
rota += random(-PI,PI);
rot = modConstrain(rot,-PI,PI);
rota = modConstrain(rota,-PI,PI);
x += cos(rot) * step;
x = modConstrain(x,0,1);
y += sin(rot) * step;
y = modConstrain(y,0,1);
}
void lookAround() {
for(int i = 0; i < groups.length; i++) {
Group cur = groups[i];
if(dist(x,y,cur.x,cur.y) < thresh && cur.hasChip() && lastchip!=i) { // if you run into a pile
if(carry) { // and you're carrying a chip
cur.add(); // drop it off
carry = false;
if(t)displayTransfer(lastchip,i);
lastchip = i;
}
else { // if you're not carrying a chip
cur.remove(); // pick it up
carry = true;
lastchip = i;
}
}
}
}
void display() {
float r = carry?3:2;
if(t) fill(color(0,carry?100:0,0,carry?20:10));
else fill(carry?150:0,0,0);
noStroke();
ellipse(x*width, y*height, r, r);
}
void displayTransfer(int a, int b) {
Group cura = groups[a];
Group curb = groups[b];
stroke(0,0,0,30);
line(cura.x*width, cura.y*height, curb.x*width, curb.y*height);
}
}
float modConstrain(float x, float low, float high) {
float diff = high - low;
x = (x - low) % diff;
if(x < 0) x += diff;
return x + low;
}
/**
<p>200 wandering souls organize 401 woodchips without any centralized planning. Click to reset, hit any key to watch the paths of woodchips over time.</p>
<p>Each wanderer follows two rules:</p>
<ul>
<li>If you run into a woodchip and don't have one, pick it up.</li>
<li>If you run into a woodchip and do have one, drop it off.</li>
</ul>
<p>Each circle represents a pile, as it gains woodchips its size increases. The wanderers turn red when they are carrying a woodchip.</p>
<p><a href="chips.mp3">Listen</a> to Chips. Each chip is a note, played while it is being moved between piles, with pitch corresponding to the pile it was removed from and dynamics to the size of that pile. From muddled chaos emerges a chord.</p>
<p><i>(Thanks to <a href="http://www.rpi.edu/~heuveb/">Bram</a> for helping me see why it converges to a single pile).</i></p>
*/
Wanderer[] wanderers = new Wanderer[200];
Group[] groups = new Group[401];
boolean t = false;
PFont font;
void setup() {
font = loadFont("Helonia-14.vlw");
for(int i = 0; i < wanderers.length; i++)
wanderers[i] = new Wanderer();
for(int i = 0; i < groups.length; i++)
groups[i] = new Group();
size(400,400);
background(255);
}
void draw() {
if(!t) background(255);
for(int i = 0; i < wanderers.length; i++) {
wanderers[i].update();
wanderers[i].display();
}
for(int i = 0; i < groups.length; i++)
groups[i].display();
}
void mousePressed() {
setup();
}
void keyPressed() {
t = t?false:true;
background(255);
}