-
Notifications
You must be signed in to change notification settings - Fork 1
/
langtonsAnt.pde
569 lines (513 loc) · 17.9 KB
/
langtonsAnt.pde
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
/*
Langton's Ant
Written by Jimmie Rodgers 9/16/15
Written in Processing 3.0b6
This is a simple two dimentional turring machine.
The "ant" will move left if the square is empty, or
right if the square is full. The now vacant square
will be toggled on/off. This version allows you
to set different colors for the ants, and they will
leave a colored space behind.
I was inspired to write this from a Numberphile video:
https://www.youtube.com/watch?v=NWBToaXK5T0
You can check the wiki page here:
https://en.wikipedia.org/wiki/Langton%27s_ant
Change varialbes below to play around with settings. Once running
the following keys work:
space = advances generationJump generations
a = toggles autoAdvance
s = saves the current frame
v = fills in all blank space till voidThreshold is met
n = new grid, clears the grid of all colors, but leave the ants
r = randomizes ant coordinates and directions, does not clear the grid
t = seeds the grid with random colors based on randomThreshold
x = starts/stops auto recording
+ = multiplies the generationJump by 10
- = divides the generationJump by 10
[ = deletes one ant
] = creates a new ant
*/
// Set the following variables to change program settings.
boolean screenSaver = true; // runs the program in fullscreen, and sets the size based on screen resolution
// screenSaver will respect the block size. Press Esc to quit.
int xSize = 3840; // number of blocks wide 3840
int ySize = 2160; // number of blocks tall 2160
int blockSize = 1; // size of each block
boolean worldWrap = true; // whether the ants wrap around or just "bounce" off the walls
boolean hasSand = false;
int numSand = 100000;
int sandXStart;
int sandYStart;
boolean hasAnts = true;
int numAnts = 70; // number of ants you want
long generationJump = 1000; // the number of generations that will jump between frames
// set it to 1 to show every one, or very high for crazy images at high res
// high generationJump can take significant processing time
boolean autoAdvance = true; // whether the frame advances automatically. Otherwise you have to press space
boolean randomColors = false; // random ant colors[] will be chosen, overides sequenceColors
boolean sequenceColors = true; // colors will be assigned sequentially (ROYGBIV is first in the set) to ants
int colorSet = 1; // 0 = rainbow, 1 = fall colors, 2 = contrast
boolean showAnts = true; // draws and Ant, best with large blockSize, not compatible with fastDraw mode
boolean showGrid = false; // better for smaller resolutions and/or large block sizes
boolean border = false; // creates a one block wide border around the image
boolean randomSeed = false; // this will seed the grid randomly with colors on startup
float randomThreshold = 1.0; // percent of cells that will be colored via randomSeed
boolean fillVoid = false; // keeps running generations at setup() till there is no space larger than voidThreshold
int voidThreshold = 100; // maximum number of sequential blank spaces before it runs a generation
// if you set voidThreshold too low, it will never stop running for large grids
boolean fastDraw = false; // fastDraw mode only re-draws the frame as needed, but you lose showAnts and showGrid
int fastFrameRate = 120; // frameRate for the fastDraw mode.
boolean autoSave = false; // will auto save each frame in the sketch directory
String fileName = "ants_#####.tif"; // the ##s will be replaced by frameCount
int maxFrames = 10000; // these can get big for larger resolutions, so be careful
// colors are all in RGB hex values
int[][] colors = {
{#FF0000, #FFA500, #FFFF00, #008000, #0000FF, #4B0082, #8A2BE2}, // rainbow colors
{#8B4513, #006400, #DAA520, #FF8C00, #556B2F, #8B0000}, // fall colors
{#FF1493, #00FFFF, #008080, #FF00FF, #7FFF00, #FFD700, #00BFFF} // pink, cyan, teal, magenta, chartreuse, gold, sky blue
};
int fillColor = #FFFFFF; // default fill the ants leave behind
int backColor = #000000; // default background color
int defAntColor = #FF0000; // default ant color, only used if neither random or sequential is selected above
int background = #909090; // boarder around the grid if enabled
int gridColor = #202020; // the grid line color if enabled
// these set the ant directions, don't mess with them.
final byte up = 0;
final byte right = 1;
final byte down = 2;
final byte left = 3;
int mouseColor; // place for the mouse color when you click, by default it will be colors[0]
int colorSelect = 0; // used to track where in colors[] the mouse color is
int borderXSize = blockSize; // used to center the border on X
int borderYSize = blockSize; // used to center the border on Y
// creates the grid and the ants
int grid[][];
Ant[] ants = new Ant[numAnts];
boolean gridHasChanged = false;
void settings() { // settings() is new in 3.0+
if (screenSaver) {
if (showAnts && !fastDraw) size(displayWidth, displayHeight, P3D); // P3D is needed for translate()
else size(displayWidth, displayHeight, P2D);
pixelDensity(displayDensity());
xSize = displayWidth/blockSize;
ySize = displayHeight/blockSize;
borderXSize = (displayWidth-(blockSize * xSize)) / 2;
borderYSize = (displayHeight-(blockSize * ySize)) / 2;
if (border == false) {
border = true;
background = backColor;
}
fullScreen();
} else {
// this sets the frame size depending on whether you want a border
int xTemp, yTemp;
if (border) {
xTemp = xSize*blockSize + 2*blockSize;
yTemp = ySize*blockSize + 2*blockSize;
} else {
xTemp = xSize*blockSize;
yTemp = ySize*blockSize;
borderXSize = 0;
borderYSize = 0;
}
if (showAnts) size(xTemp, yTemp, P3D);
else size(xTemp, yTemp, P2D);
}
}
void setup() {
if (fastDraw) {
frameRate(fastFrameRate);
showGrid = false;
showAnts = false;
}
grid = new int[xSize][ySize];
zeroGrid();
// actually creates the ants
if (hasAnts) {
for (int i = 0; i < ants.length; i++) {
int tempColor = int(random(colors[colorSet].length));
if (randomColors) ants[i] = new Ant(colors[colorSet][tempColor], #FFFFFF);
else if (sequenceColors) ants[i] = new Ant(colors[colorSet][i%colors[colorSet].length], #FFFFFF);
else ants[i] = new Ant();
}
}
if (hasSand) {
sandXStart = xSize/2;
sandYStart = ySize/2;
grid[sandXStart][sandYStart] = numSand;
gridHasChanged = true;
}
if (randomSeed) randomSeedGrid(); // seeds the grid randomly if selected
if (fillVoid && hasAnts) intoTheVoid(); // fills the initial frame up if that option is selected
mouseColor = colors[colorSet][colorSelect]; // sets the mouse color
showGrid();
if (hasAnts && showAnts && !fastDraw) {
for (int i = 0; i < ants.length; i++) ants[i].show();
gridHasChanged = true;
}
}
void draw() {
if (hasAnts && autoAdvance) advanceGenerations();
if (hasSand && autoAdvance) moveSand();
if (autoSave) if (frameCount <= maxFrames) saveFrame(fileName);
if (gridHasChanged && !fastDraw) showGrid();
else if (!fastDraw) noLoop();
}
void keyPressed() {
if (key == 's') saveFrame(fileName);
if (key == ' ') {
if (hasAnts)advanceGenerations();
if (hasSand)moveSand();
}
if (key == 'a') {
autoAdvance = !autoAdvance;
if (autoAdvance) loop();
}
if (key == 'n') {
zeroGrid();
if (fastDraw) showGrid();
}
if (key == '+') generationJump *= 2;
if (key == '-') {
generationJump /= 2;
if (generationJump < 1) generationJump = 1;
}
if (key == 'v') intoTheVoid();
if (key == 'r') {
for (int i = 0; i < ants.length; i++) ants[i].randomDirection();
changeGrid();
}
if (key == 'x') autoSave = !autoSave;
if (key == 't') {
randomSeedGrid();
if (fastDraw) showGrid();
}
if (key == '[') {
if (ants.length > 0) {
ants = (Ant[]) shorten(ants);
changeGrid();
}
}
if (key == ']') {
int tempColor = int(random(colors[colorSet].length));
ants = (Ant[]) expand(ants, ants.length+1);
if (randomColors) ants[ants.length-1] = new Ant(colors[colorSet][tempColor], #FFFFFF);
else if (sequenceColors)
ants[ants.length-1] = new Ant(colors[colorSet][(ants.length-1)%colors[colorSet].length], #FFFFFF);
changeGrid();
}
}
void mousePressed() {
if (mouseButton == LEFT) {
if (border) grid[(mouseX-borderXSize)/blockSize][(mouseY-borderYSize)/blockSize] = mouseColor;
else grid[mouseX/blockSize][mouseY/blockSize] = mouseColor;
changeGrid();
}
if (mouseButton == RIGHT) {
colorSelect = (colorSelect+1) % colors.length;
mouseColor = colors[colorSet][colorSelect];
}
}
void mouseDragged() {
if (mouseButton == LEFT) {
if (border) grid[(mouseX-borderXSize)/blockSize][(mouseY-borderYSize)/blockSize] = mouseColor;
else grid[mouseX/blockSize][mouseY/blockSize] = mouseColor;
changeGrid();
}
}
void moveSand () {
for (int count = 0; count < generationJump; count++) {
int tempGrid[][] = new int[xSize][ySize];
for (int x = 0; x < xSize; x++) {
for (int y = 0; y < ySize; y++) {
tempGrid[x][y] += grid[x][y] % 4;
if (grid[x][y] >= 4) {
int tempVal = grid[x][y] / 4;
if (x == 0) {
if (worldWrap) tempGrid[xSize][y] += tempVal;
} else tempGrid[x-1][y] += tempVal;
if (x == xSize - 1) {
if (worldWrap) tempGrid[0][y] += tempVal;
} else tempGrid[x+1][y] += tempVal;
if (y == 0) {
if (worldWrap) tempGrid[x][ySize] += tempVal;
} else tempGrid[x][y-1] += tempVal;
if (y == ySize - 1) {
if (worldWrap) tempGrid[x][0] += tempVal;
} else tempGrid[x][y+1] += tempVal;
}
}
}
for (int x = 0; x < xSize; x++)
for (int y = 0; y < ySize; y++)
grid[x][y] = tempGrid[x][y];
}
//println("Moved Sand");
changeGrid();
}
// Used to indicate that the grid needs to be shown, and for loop to resume
void changeGrid() {
gridHasChanged = true;
loop();
}
// will advance all ants generationJump generations of movement
void advanceGenerations() {
for (int count = 0; count < generationJump; count++) {
for (int i = 0; i < ants.length; i++) {
ants[i].move();
}
}
changeGrid();
}
// Randomly seeds the grid with random colored spaces. Great for more complex movements.
void randomSeedGrid() {
int percent = 100;
while (true) {
if (randomThreshold < 1) {
percent *= 10;
randomThreshold *= 10;
} else break;
}
for (int x = 0; x < xSize; x++)
for (int y = 0; y < ySize; y++)
if (random(percent) <= randomThreshold) {
int tempColor = int(random(colors[colorSet].length));
grid[x][y] = colors[colorSet][tempColor];
}
changeGrid();
}
// this will keep calling advanceGenerations() till there are fewer than voidThreshold blank spaces
void intoTheVoid() {
int count = 0;
while (true) {
for (int x = 0; x < xSize; x++) {
count = 0;
for (int y = 0; y < ySize; y++) {
if (grid[x][y] == 0) count++;
else if (grid[x][y] > 0) count = 0;
if (count > voidThreshold) {
advanceGenerations();
count = 0;
}
}
}
if (count < voidThreshold) break;
else count = 0;
}
if (fastDraw) showGrid();
}
// displays the grid
void showGrid() {
clear();
background(background);
if (showGrid) stroke(gridColor);
else noStroke();
for (int x = 0; x < xSize; x++) {
for (int y = 0; y < ySize; y++) {
if (grid[x][y] == 0) fill(backColor);
else if (hasAnts) fill(grid[x][y]);
else if (hasSand) {
if (grid[x][y] >= 4) fill(colors[colorSet][3]);
else {
int temp = grid[x][y];
fill(colors[colorSet][temp-1]);
}
}
if (border) rect(x*blockSize + borderXSize, y*blockSize + borderYSize, blockSize, blockSize);
else rect(x*blockSize, y*blockSize, blockSize, blockSize);
}
}
if (showAnts && hasAnts) for (int i = 0; i < ants.length; i++) ants[i].show();
gridHasChanged = false;
}
// clears all colored spaces, but does nothing to the ants
void zeroGrid() {
for (int x = 0; x < xSize; x++)
for (int y = 0; y < ySize; y++)
grid[x][y] = 0;
changeGrid();
}
// the Ant will follow the basic Lanton's Ant rules when asked nicely.
class Ant {
private int antX; // current X location of the Ant
private int antY; // current Y location of the Ant
private int antDirection;// where the Ant is currently looking, though maybe not what
private int antColor; // the color the Ant displays when being shown
private int antFill; // the color the Ant leave behind in a blank space
// a default ant starts at a random position and direction
Ant () {
antColor = defAntColor;
antFill = fillColor;
antX = int(random(xSize));
antY = int(random(ySize));
antDirection = int(random(4));
}
// 3 ints, and you've set location and direction
Ant (int tempX, int tempY, int dirTemp) {
antColor = defAntColor;
antFill = fillColor;
antX = tempX;
antY = tempY;
antDirection = dirTemp;
}
// 2 ints, and you've set what color the Ant fills in, and what color the Ant is when shown
Ant (int tempFillColor, int tempAntColor) {
antColor = tempAntColor;
antFill = tempFillColor;
antX = int(random(xSize));
antY = int(random(ySize));
antDirection = int(random(4));
}
// the Ant will move according to the Langton's Ant rules.
void move() {
if (fastDraw) {
noStroke();
if (grid[antX][antY] == 0) fill(antFill);
else fill(backColor);
rect(antX*blockSize, antY*blockSize, blockSize, blockSize);
}
if (antDirection == up) {
if (grid[antX][antY] == 0) {
antDirection = left;
grid[antX][antY] = antFill;
antX--;
} else {
antDirection = right;
grid[antX][antY] = 0;
antX++;
}
} else if (antDirection == right) {
if (grid[antX][antY] == 0) {
antDirection = up;
grid[antX][antY] = antFill;
antY--;
} else {
antDirection = down;
grid[antX][antY] = 0;
antY++;
}
} else if (antDirection == down) {
if (grid[antX][antY] == 0) {
antDirection = right;
grid[antX][antY] = antFill;
antX++;
} else {
antDirection = left;
grid[antX][antY] = 0;
antX--;
}
} else if (antDirection == left) {
if (grid[antX][antY] == 0) {
antDirection = down;
grid[antX][antY] = antFill;
antY++;
} else {
antDirection = up;
grid[antX][antY] = 0;
antY--;
}
}
// if worldWrap is enabled, the Ant will pop over to the other side. Otherwise it will turn about-face when hitting a wall
if (worldWrap) {
if (antX < 0) antX = xSize-1;
else if (antX > xSize-1) antX = 0;
if (antY < 0) antY = ySize-1;
else if (antY > ySize-1) antY = 0;
} else {
if (antX < 0) {
antX = 0;
antDirection = right;
} else if (antX > xSize-1) {
antX = xSize-1;
antDirection = left ;
}
if (antY < 0) {
antY = 0;
antDirection = down;
} else if (antY > ySize-1) {
antY = ySize-1;
antDirection = up;
}
}
}
// shows the current location of the Ant, by drawing an Ant
void show() {
fill(antColor);
int xTemp = int(antX*blockSize + blockSize*0.5 + borderXSize);
int yTemp = int(antY*blockSize + blockSize*0.5 + borderYSize);
pushMatrix();
translate(xTemp, yTemp);
switch(antDirection) {
case up:
rotateZ(0);
break;
case left:
rotateZ(-HALF_PI);
break;
case right:
rotateZ(HALF_PI);
break;
case down:
rotateZ(PI);
break;
}
strokeWeight(blockSize/75);
ellipseMode(CENTER);
fill(antFill);
ellipse(0, -blockSize*0.21, blockSize*0.2, blockSize*0.175);
ellipse(0, 0, blockSize/4, blockSize/4);
ellipse(0, blockSize*0.31, blockSize/4, blockSize*0.375);
fill(#FFFFFF);
noStroke();
ellipse(-blockSize*0.05, -blockSize*0.19, blockSize*0.025, blockSize*0.02);
ellipse(blockSize*0.05, -blockSize*0.19, blockSize*0.025, blockSize*0.02);
noFill();
stroke(blockSize/70);
strokeWeight(blockSize/70);
arc(blockSize*0.11, -blockSize*0.125, blockSize/8, blockSize/8, 0, HALF_PI);
arc(-blockSize*0.11, -blockSize*0.125, blockSize/8, blockSize/8, HALF_PI, PI);
arc(blockSize*0.125, -blockSize*0.09, blockSize/8, blockSize/8, 0, HALF_PI);
arc(-blockSize*0.125, -blockSize*0.09, blockSize/8, blockSize/8, HALF_PI, PI);
arc(blockSize*0.125, blockSize*0.09, blockSize/8, blockSize/8, PI+HALF_PI, TWO_PI);
arc(-blockSize*0.125, blockSize*0.09, blockSize/8, blockSize/8, PI, PI+HALF_PI);
arc(blockSize*0.11, blockSize*0.125, blockSize/8, blockSize/8, PI+HALF_PI, TWO_PI);
arc(-blockSize*0.11, blockSize*0.125, blockSize/8, blockSize/8, PI, PI+HALF_PI);
arc(0, -blockSize*0.20, blockSize/6, blockSize/8, PI+QUARTER_PI, TWO_PI-QUARTER_PI);
rotateZ(0);
popMatrix();
}
// moves the Ant to a random location and direction. It may be disoriented, but it's an Ant, so I doubt it cares too much.
void randomDirection() {
antX = int(random(xSize));
antY = int(random(ySize));
antDirection = int(random(4));
}
void changeColor(int tempColor) {
antColor = tempColor;
}
void changeFill(int tempColor) {
antFill = tempColor;
}
}
// Debating on building a grid class, but not sure what advantage that would yeild. Leaving this here for now.
// It is currently unused though.
class Grid {
int sizeX;
int sizeY;
int startX;
int startY;
int blockColor;
int lineColor;
boolean hasLines;
int gridArray[][];
Grid (int blockTemp) {
}
Grid (int sizeXTemp, int sizeYTemp, int bockTemp) {
}
private void buildGrid() {
}
void update(int xTemp, int yTemp, int colorTemp) {
}
void fullRedraw() {
}
}