├── .gitignore ├── LICENSE ├── README.md └── evolving_ant_farm └── evolving_ant_farm.pde /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | applet 3 | application.linux32 4 | application.linux64 5 | application.windows32 6 | application.windows64 7 | application.macosx 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 AdrianMargel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Evolving Ant Farm 2 | A version of langton's ant with many ants that evolve their rule sets and compete for resources 3 | 4 | ![colorful ant farm](https://i.imgur.com/guzDMFw.png) 5 | 6 | This code is able to simulate a colony of langton's ants. Each ant has their own set of rules for how to move and change the environment. These ants will overtime will adapt to better survive in their environment. Each ant has a limited lifetime and must find food to reproduce. Food will only spawn on tiles with certain criteria and so ants must either create or find environments where food is able to grow if they wish to survive. 7 | 8 | As one species of ant begins to become successful in a niche it often changes the environment openning up new niches. Species will evolve, create ecosystems compete with other species and sometimes go extinct until a somewhat stable state is reached. Each ant on it's own is ridiculously simple made up of just a set of simple rules to guide their movement but with such a large number of ants complex emergent behaviours arise. 9 | 10 | This is a test in artificial life and evolutionary algorithms meant to mimic the evolution of simple life forms like mold. 11 | 12 | The code was written fall 2018. 13 | 14 | ## more images: 15 | ![huge ant farm](https://i.imgur.com/ph8sXBc.png) 16 | ![huge ant farm](https://i.imgur.com/jLuip2v.png) 17 | ![circle ant farm](https://i.imgur.com/ejpURdr.png) 18 | ![orange ant farm](https://i.imgur.com/XB6Gb0x.png) 19 | ![basic ant farm](https://i.imgur.com/OzQ3X7P.png) 20 | ![basic ant farm](https://i.imgur.com/s56FlL4.png) 21 | -------------------------------------------------------------------------------- /evolving_ant_farm/evolving_ant_farm.pde: -------------------------------------------------------------------------------- 1 | /* 2 | Evolving Ant Farm 3 | ----------------- 4 | This program simulates a colony of langton ants. These ants however will age and die overtime and can only reproduce by eating. 5 | When an ant reproduces its offspring will mutate slightly. This leads to the evolution of unique species which will compete. 6 | 7 | written by Adrian Margel, Fall 2018 8 | */ 9 | 10 | 11 | //--------------------------------- 12 | // Basic Settings 13 | //--------------------------------- 14 | 15 | //if food is displayed 16 | boolean displayFood=false; 17 | //if the color of the ant is displayed or the trail itself 18 | boolean displayColor=true; 19 | //if the ants move in eight directions or four 20 | boolean eightDirections=true; 21 | //if the food spawns in a disk 22 | boolean foodDisk=false; 23 | //if the map will have varied spawning rules for food (biomes) 24 | boolean multiBiome=true; 25 | //how transparent the ants are (0 to 255) 26 | int opacity=20; 27 | //redraw the an ant's tiles after it dies 28 | boolean redrawDead=false; 29 | //how fast the hue of a species changes 30 | float hueChange=3; 31 | //how fast the screen fades to black (0 to 255, generally works best set to 0 for no fade) 32 | int fade=0; 33 | 34 | //the number of different types of tiles 35 | int tileTypes=5; 36 | 37 | //--------------------------------- 38 | 39 | 40 | 41 | //the map of all tiles 42 | Tile[][] tiles; 43 | 44 | //all alive ants 45 | ArrayList ants; 46 | 47 | //how much the camera is zoomed in 48 | float zoom=1; 49 | 50 | void setup() { 51 | //setup window size 52 | size(800, 800); 53 | //set color to use hue 54 | colorMode(HSB); 55 | 56 | //setup map to size of screen 57 | tiles=new Tile[(int)(width/zoom)][(int)(height/zoom)]; 58 | for (int x=0; x(); 66 | for (int i=0; i<5000; i++) { 67 | spawnRandomAnt(); 68 | } 69 | 70 | //draw starting tiles 71 | background(0); 72 | /*for (int x=0; x0){ 86 | fill(0,fade); 87 | noStroke(); 88 | rect(0,0,width,height); 89 | } 90 | //try to spawn up to a number of new food tiles 91 | for (int i=0; i<100; i++) { 92 | spawnFood(tiles); 93 | } 94 | 95 | //move all ants 96 | for (int i=0; i=0; i--) { 102 | if (!ants.get(i).alive) { 103 | ants.get(i).die(); 104 | ants.remove(i); 105 | } 106 | } 107 | } 108 | 109 | 110 | 111 | //-------------General Methods------------- 112 | 113 | 114 | 115 | //spawns food onto the map 116 | void spawnFood(Tile[][] grid) { 117 | //if the tile was successfully placed 118 | boolean placed=false; 119 | //the number of times it will attempt to place the food 120 | int tries=100; 121 | while (!placed&&tries>0) { 122 | //how many more times it will try to place the food 123 | tries--; 124 | 125 | int x, y; 126 | //spawn food 127 | if (!foodDisk) { 128 | //spawn food in square 129 | x=(int)random(0, tiles.length); 130 | y=(int)random(0, tiles[0].length); 131 | } else { 132 | //spawning food in a circle 133 | float d=random(0, width/zoom/2); 134 | float a=random(0, TWO_PI); 135 | x=(int)(cos(a)*d)+(int)(width/zoom/2); 136 | y=(int)(sin(a)*d)+(int)(width/zoom/2); 137 | } 138 | 139 | //if food somehow spawns out of bounds loop it around back into bounds 140 | if (x<0) { 141 | x=x+grid.length; 142 | } 143 | if (x>=grid.length) { 144 | x=x-grid.length; 145 | } 146 | if (y<0) { 147 | y=y+grid[0].length; 148 | } 149 | if (y>=grid[0].length) { 150 | y=y-grid[0].length; 151 | } 152 | 153 | //calculate using a simple set of rules if the food can spawn at this position 154 | //this is based on the amount of air around the tile 155 | 156 | if (multiBiome) { 157 | //rules vary over space creating multiple biomes 158 | int surr=getSurround(tiles, 0, new Vector(x, y), 4); 159 | if (grid[x][y].type==0&&(surr/zoom>x/10&&surr/zoom20&&surr<25) { 176 | //if the food can spawn then replace the tile with food 177 | grid[x][y].resetTile(); 178 | grid[x][y].type=tileTypes; 179 | //draw the food optionally 180 | if (displayFood) { 181 | fill(100, 255, 150); 182 | noStroke(); 183 | rect(x*zoom, y*zoom, max(zoom,1), max(zoom,1)); 184 | } 185 | //set flag that the tile was placed 186 | placed=true; 187 | } 188 | } 189 | } 190 | } 191 | 192 | //get the number of tiles surrounding a tile 193 | int getSurround(Tile[][] grid, int fType, Vector pos, int range) { 194 | int total=0; 195 | for (int tx=-range; tx<=range; tx++) { 196 | for (int ty=-range; ty<=range; ty++) { 197 | int x=pos.x+tx; 198 | int y=pos.y+ty; 199 | if (x<0) { 200 | x=x+grid.length; 201 | } 202 | if (x>=grid.length) { 203 | x=x-grid.length; 204 | } 205 | if (y<0) { 206 | y=y+grid[0].length; 207 | } 208 | if (y>=grid[0].length) { 209 | y=y-grid[0].length; 210 | } 211 | if (grid[x][y].type==fType) { 212 | total++; 213 | } 214 | } 215 | } 216 | return total; 217 | } 218 | 219 | //spawn new ants 220 | void spawnRandomAnt() { 221 | ants.add(new Ant(new Vector((int)random(0, tiles.length), (int)random(0, tiles[0].length)))); 222 | } 223 | void spawnFromRandom(ArrayList options) { 224 | int id=(int)random(0, options.size()); 225 | spawnAnt(options.get(id), new Vector(options.get(id).pos)); 226 | } 227 | void spawnAnt(Ant parAnt, Vector pos) { 228 | ants.add(new Ant(parAnt, pos)); 229 | ants.get(ants.size()-1).mutate(); 230 | } 231 | 232 | 233 | 234 | //-------------Classes------------- 235 | 236 | 237 | 238 | //simple integer based vector class 239 | class Vector { 240 | int x, y; 241 | Vector(int tx, int ty) { 242 | x=tx; 243 | y=ty; 244 | } 245 | Vector(Vector clone) { 246 | x=clone.x; 247 | y=clone.y; 248 | } 249 | boolean isSame(Vector compare) { 250 | return compare.x==x&&compare.y==y; 251 | } 252 | } 253 | 254 | //this class generates random numbers to be used in mutations 255 | //the mutator class is also able to be mutated 256 | class Mutator { 257 | 258 | //maximum value to be produced 259 | float high; 260 | //other values for the math equation used to generate numbers 261 | float spread; 262 | float modifier; 263 | 264 | //if true it cannot mutate it's high value 265 | boolean fixedHigh; 266 | 267 | Mutator(float s, float h, float mod) { 268 | 269 | spread=s; 270 | high=h; 271 | modifier=mod; 272 | fixedHigh=false; 273 | } 274 | 275 | //create a mutator based off another mutator 276 | Mutator(Mutator clone) { 277 | 278 | spread=clone.spread; 279 | high=clone.high; 280 | modifier=clone.modifier; 281 | fixedHigh=clone.fixedHigh; 282 | } 283 | 284 | //set high to be fixed 285 | void fixHigh() { 286 | fixedHigh=true; 287 | } 288 | 289 | //mutate the mutator based on other mutators 290 | void mutate(Mutator mutateMutateHigh, Mutator mutateMutateSpread, Mutator mutateMutateMod) { 291 | if (!fixedHigh) { 292 | if ((int)random(0, 2)==1) { 293 | high+=mutateMutateHigh.getValue(); 294 | } else { 295 | high-=mutateMutateHigh.getValue(); 296 | } 297 | } 298 | high=max(high, 0.001); 299 | 300 | if ((int)random(0, 2)==1) { 301 | spread+=mutateMutateSpread.getValue(); 302 | } else { 303 | spread-=mutateMutateSpread.getValue(); 304 | } 305 | spread=min(max(spread, 1), 10); 306 | 307 | if ((int)random(0, 2)==1) { 308 | modifier+=mutateMutateMod.getValue(); 309 | } else { 310 | modifier-=mutateMutateMod.getValue(); 311 | } 312 | modifier=max(modifier, 0); 313 | } 314 | 315 | //get the value for a seed number from 0 to 1 316 | float getValue(float in) { 317 | float val=(pow(in, spread)*pow(high, 2)+in*modifier*high)/(high+modifier); 318 | return val; 319 | } 320 | 321 | //get a float value 322 | float getValue() { 323 | float x=random(0, 1); 324 | return getValue(x); 325 | } 326 | 327 | //get an int value 328 | int getIntValue() { 329 | int temp=(int)getValue(); 330 | return temp; 331 | } 332 | } 333 | 334 | //this class checks for the existance of a certain tile type at a certain relative position 335 | class Find { 336 | //the relative position of the tile to be checked 337 | Vector pos; 338 | //the type of tile expected 339 | int type; 340 | 341 | Find(Find clone) { 342 | type=clone.type; 343 | pos=new Vector(clone.pos); 344 | } 345 | Find(Vector p, int t) { 346 | pos=new Vector(p); 347 | type=t; 348 | } 349 | 350 | //returns true if the tile type matches the expected type at position searched 351 | //the pos will be rotated based on direction to ensure that ants cannot form directional biases 352 | boolean matches(Tile[][] grid, Vector p, int direction) { 353 | int xAdd=0; 354 | int yAdd=0; 355 | if(eightDirections){ 356 | direction/=2; 357 | if (direction==0) { 358 | xAdd=pos.x; 359 | yAdd=pos.y; 360 | } else if (direction==1) { 361 | xAdd=-pos.y; 362 | yAdd=pos.x; 363 | } else if (direction==2) { 364 | xAdd=-pos.x; 365 | yAdd=-pos.y; 366 | } else if (direction==3) { 367 | xAdd=pos.y; 368 | yAdd=-pos.x; 369 | } 370 | }else{ 371 | if (direction==0) { 372 | xAdd=pos.x; 373 | yAdd=pos.y; 374 | } else if (direction==1) { 375 | xAdd=-pos.y; 376 | yAdd=pos.x; 377 | } else if (direction==2) { 378 | xAdd=-pos.x; 379 | yAdd=-pos.y; 380 | } else if (direction==3) { 381 | xAdd=pos.y; 382 | yAdd=-pos.x; 383 | } 384 | } 385 | 386 | int x=p.x+xAdd; 387 | int y=p.y+yAdd; 388 | 389 | if (x<0) { 390 | x=x+grid.length; 391 | } 392 | if (x>=grid.length) { 393 | x=x-grid.length; 394 | } 395 | if (y<0) { 396 | y=y+grid[0].length; 397 | } 398 | if (y>=grid[0].length) { 399 | y=y-grid[0].length; 400 | } 401 | if (x>=0&&y>=0&&x search; 412 | //the type that will be created if the searched tiles are found 413 | int newType; 414 | //how much the ant will turn by 415 | int turn; 416 | //if the rule is going to be added, this is set to false if the rule discovers it is a duplicate of an existing rule 417 | boolean alive; 418 | 419 | //create rule as a copy of another rule 420 | Rule(Rule clone) { 421 | alive=true; 422 | turn=clone.turn; 423 | newType=clone.newType; 424 | search=new ArrayList(); 425 | for (int i=0; i s, int nt, int t) { 430 | search=s; 431 | newType=nt; 432 | turn=t; 433 | alive=true; 434 | for (int i=0; i rules; 467 | 468 | //the ant's position 469 | Vector pos; 470 | //the direction the ant is facing 471 | int direction; 472 | 473 | //the color of the ant 474 | int hue; 475 | //if the ant is alive 476 | boolean alive; 477 | //the id of the tile that will kill the ant 478 | //each ant is forced to find at least one tile type poisonous 479 | int weakness; 480 | 481 | //all tiles the ant has created 482 | ArrayList claimed; 483 | 484 | //the mutators for the ant 485 | //these allow the ants to evolve over time 486 | Mutator addMut; 487 | Mutator rangeMut; 488 | Mutator remMut; 489 | Mutator shiftMut; 490 | Mutator shiftDistMut; 491 | Mutator complexMut; 492 | Mutator spreadMut; 493 | Mutator ageMut; 494 | Mutator mutateMutateHigh; 495 | Mutator mutateMutateSpread; 496 | Mutator mutateMutateMod; 497 | 498 | //create an ant from a parent 499 | Ant(Ant parAnt, Vector p) { 500 | //set random direction 501 | if(eightDirections){ 502 | direction=(int)random(0, 8); 503 | }else{ 504 | direction=(int)random(0, 4); 505 | } 506 | //init claimed arraylist 507 | claimed=new ArrayList(); 508 | //set base stats to same as parent 509 | weakness=parAnt.weakness; 510 | ageMax=parAnt.ageMax; 511 | age=ageMax; 512 | pos=new Vector(p); 513 | //set to be alive 514 | alive=true; 515 | //set the hue to be almost the same as the parent so that species are the same color 516 | hue=(parAnt.hue+(int)random(-hueChange, hueChange))%256; 517 | if (hue<0) { 518 | hue+=256; 519 | } 520 | 521 | //add rules 522 | rules=new ArrayList(); 523 | for (int i=0; i(); 568 | changeWeakness(); 569 | ageMax=1000; 570 | age=ageMax; 571 | alive=true; 572 | hue=(int)random(0, 256); 573 | pos=new Vector(p); 574 | 575 | //startup rules 576 | rules=new ArrayList(); 577 | for (int i=0; i<30; i++) { 578 | addRuleStart(); 579 | } 580 | } 581 | 582 | //mutate the ants 583 | void mutate() { 584 | mutMuts(); 585 | removeRules(); 586 | addRules(); 587 | shiftRules(); 588 | 589 | /* 590 | //this code allows age to evolve 591 | int ageChange=ageMut.getIntValue(); 592 | if(ageMutMut.getValue()>0.5){ 593 | ageChange*=-1; 594 | } 595 | ageMax+=ageChange; 596 | */ 597 | 598 | //very rarely allow weakness to change 599 | if (random(0, 1000)<1) { 600 | changeWeakness(); 601 | } 602 | } 603 | 604 | //mutate the mutators 605 | void mutMuts() { 606 | addMut.mutate(mutateMutateHigh, mutateMutateSpread, mutateMutateMod); 607 | rangeMut.mutate(mutateMutateHigh, mutateMutateSpread, mutateMutateMod); 608 | remMut.mutate(mutateMutateHigh, mutateMutateSpread, mutateMutateMod); 609 | shiftMut.mutate(mutateMutateHigh, mutateMutateSpread, mutateMutateMod); 610 | shiftDistMut.mutate(mutateMutateHigh, mutateMutateSpread, mutateMutateMod); 611 | complexMut.mutate(mutateMutateHigh, mutateMutateSpread, mutateMutateMod); 612 | spreadMut.mutate(mutateMutateHigh, mutateMutateSpread, mutateMutateMod); 613 | ageMut.mutate(mutateMutateHigh, mutateMutateSpread, mutateMutateMod); 614 | } 615 | 616 | //randomly add a random number of new rules 617 | void addRules() { 618 | int add=addMut.getIntValue(); 619 | for (int i=0; i temp; 621 | Rule tempRule; 622 | temp = new ArrayList(); 623 | int rSize=(int)random(1, complexMut.getIntValue()+1); 624 | for (int j=0; j temp; 649 | Rule tempRule; 650 | temp = new ArrayList(); 651 | int rSize=(int)random(1, 10); 652 | for (int j=0; j=map.length) { 778 | pos.x=0; 779 | } 780 | if (pos.y<0) { 781 | pos.y=map[pos.x].length-1; 782 | } 783 | if (pos.y>=map[pos.x].length) { 784 | pos.y=0; 785 | } 786 | 787 | //if it is on food eat the food and make a child 788 | if (map[pos.x][pos.y].type==tileTypes) { 789 | map[pos.x][pos.y].type=0; 790 | spawnAnt(this, new Vector(pos)); 791 | 792 | //re-draw the food tile as empty 793 | fill(0,opacity); 794 | noStroke(); 795 | rect(pos.x*zoom, pos.y*zoom, max(zoom,1), max(zoom,1)); 796 | } 797 | 798 | //cause ant to age 799 | age--; 800 | //cause ant to age faster depending on the amount of empty tiles around it, this makes it harder for ants to spread 801 | //keep in mind food only spawns in tiles with space around them 802 | age-=abs(getSurround(map, 0)); 803 | 804 | //if it's age reaches 0 kill the ant 805 | if (age<0) { 806 | alive=false; 807 | 808 | //if the ant is on a tile it is weak to die 809 | } else if (map[pos.x][pos.y].die(this, weakness)) { 810 | alive=false; 811 | } 812 | } 813 | 814 | //get the number of a tile around the ant of a certain type 815 | int getSurround(Tile[][] grid, int fType) { 816 | int total=0; 817 | for (int tx=-2; tx<=2; tx++) { 818 | for (int ty=-2; ty<=2; ty++) { 819 | int x=pos.x+tx; 820 | int y=pos.y+ty; 821 | if (x<0) { 822 | x=x+grid.length; 823 | } 824 | if (x>=grid.length) { 825 | x=x-grid.length; 826 | } 827 | if (y<0) { 828 | y=y+grid[0].length; 829 | } 830 | if (y>=grid[0].length) { 831 | y=y-grid[0].length; 832 | } 833 | if (grid[x][y].type==fType||grid[x][y].type==tileTypes) { 834 | total++; 835 | } 836 | } 837 | } 838 | return total; 839 | } 840 | 841 | //kill the ant 842 | //redraws all the tiles the ant has covered as dead tiles and resets their owner 843 | void die() { 844 | noStroke(); 845 | for (Tile t : claimed) { 846 | //if the ant still owns the tile reset and redraw the tile 847 | if (t.isOwner(this)) { 848 | t.resetTile(); 849 | if(redrawDead){ 850 | fill(t.type*255/tileTypes); 851 | rect(t.pos.x*zoom, t.pos.y*zoom, max(zoom,1), max(zoom,1)); 852 | } 853 | } 854 | } 855 | } 856 | } 857 | 858 | //the tiles the map is made of, act as the environment 859 | class Tile { 860 | //the type of tile 861 | int type; 862 | //the last alive ant to change the tile 863 | Ant owner; 864 | //position of the tile 865 | Vector pos; 866 | 867 | Tile(int t, Vector p) { 868 | pos=p; 869 | owner=null; 870 | type=t; 871 | } 872 | 873 | //have an ant change the tile type 874 | void setTile(int t, Ant o) { 875 | type=t; 876 | owner=o; 877 | } 878 | 879 | //reset the tile owner 880 | void resetTile() { 881 | owner=null; 882 | //unused to set tile back to being empty 883 | //type=0; 884 | } 885 | 886 | //check if an ant is the owner 887 | boolean isOwner(Ant test) { 888 | if (owner==null) { 889 | return false; 890 | } 891 | return test==owner; 892 | } 893 | 894 | //check it the tile has an alive owner 895 | boolean claimed() { 896 | return owner!=null&&owner.alive; 897 | } 898 | 899 | //check if an ant will die on this tile 900 | boolean die(Ant test, int weakness) { 901 | if (type!=weakness) { 902 | return false; 903 | } 904 | if (owner==null) { 905 | return true; 906 | } 907 | return test!=owner; 908 | } 909 | } 910 | --------------------------------------------------------------------------------