├── Coin.js ├── index.html ├── Tile.js ├── Solid.js ├── Node.js ├── README.md ├── Brain.js ├── Dot.js ├── setting_level.js ├── Population.js ├── Player.js ├── sketch.js └── libraries └── p5.dom.js /Coin.js: -------------------------------------------------------------------------------- 1 | class Coin { 2 | constructor(x,y){ 3 | this.taken = false; 4 | this.pos = createVector(x,y); 5 | this.diameter = tileSize/2.0; 6 | } 7 | 8 | show(){ 9 | if(!showedCoin && !this.taken){ 10 | stroke(0); 11 | fill(255,230,230); 12 | ellipse(this.pos.x,this.pos.y,this.diameter); 13 | showedCoin = true; 14 | } 15 | 16 | } 17 | 18 | collides(ptl, pbr) {//player dimensions 19 | if(this.taken){ return false;} 20 | 21 | var topLeft = createVector(this.pos.x - this.diameter/2, this.pos.y-this.diameter/2); 22 | var bottomRight = createVector(this.pos.x + this.diameter/2, this.pos.y + this.diameter/2); 23 | if ((ptl.x topLeft.x) &&( ptl.y < bottomRight.y && pbr.y > topLeft.y)) { 24 | 25 | this.taken = true; 26 | return; 27 | 28 | } 29 | return; 30 | } 31 | 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Adam A.I. 5 |

ADAM A.I.

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | -------------------------------------------------------------------------------- /Tile.js: -------------------------------------------------------------------------------- 1 | class Tile{ 2 | 3 | constructor(x,y){ 4 | this.matrixPos = createVector(x,y); 5 | this.pixelPos = createVector(x*tileSize+xoff, y*tileSize+yoff); 6 | this.safe = false; 7 | this.goal = false; 8 | this.wall = false; 9 | this.edges = []; 10 | 11 | } 12 | 13 | show(){ 14 | if ((this.matrixPos.x + this.matrixPos.y) % 2 ==0) { 15 | fill(230,230,255); 16 | } else { 17 | fill(230,230,255); 18 | } 19 | if (this.wall) { 20 | fill(230,230,255); 21 | } 22 | if (this.goal || this.safe) { 23 | fill(0, 0, 0); 24 | } 25 | noStroke(); 26 | rect(this.pixelPos.x,this.pixelPos.y,tileSize,tileSize); 27 | 28 | } 29 | 30 | showEdges(){ 31 | for (var i = 0; i< this.edges.length; i++) { 32 | stroke(0); 33 | strokeWeight(4); 34 | switch(this.edges[i]) { 35 | case 4: 36 | line(this.pixelPos.x, this.pixelPos.y, this.pixelPos.x+tileSize,this.pixelPos.y); 37 | break; 38 | case 1: 39 | line(this.pixelPos.x+tileSize, this.pixelPos.y, this.pixelPos.x+tileSize, this.pixelPos.y+tileSize); 40 | break; 41 | case 2: 42 | line(this.pixelPos.x, this.pixelPos.y+tileSize, this.pixelPos.x+tileSize, this.pixelPos.y+tileSize); 43 | break; 44 | case 3: 45 | line(this.pixelPos.x, this.pixelPos.y, this.pixelPos.x, this.pixelPos.y+tileSize); 46 | break; 47 | } 48 | } 49 | } 50 | 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Solid.js: -------------------------------------------------------------------------------- 1 | class Solid { 2 | 3 | constructor(topL,botR){ 4 | var lineWidth = 1; 5 | this.pos = createVector(topL.pixelPos.x-lineWidth, topL.pixelPos.y-lineWidth); 6 | this.w = botR.pixelPos.x - this.pos.x + lineWidth; 7 | this.h = botR.pixelPos.y - this.pos.y + lineWidth; 8 | this.bottomRight = createVector(this.pos.x + this.w, this.pos.y + this.h); 9 | 10 | } 11 | 12 | 13 | restrictMovement(tl, br, movement) {//player dimensions 14 | //add the x first 15 | 16 | var x = movement.x; 17 | var y = movement.y; 18 | // 19 | // movement.x = round(movement.x); 20 | // movement.y = round(movement.y); 21 | var ptl = createVector(tl.x+movement.x, tl.y); 22 | var pbr = createVector(br.x+movement.x, br.y); 23 | 24 | if ((ptl.x this.pos.x) &&( ptl.y < this.bottomRight.y && pbr.y > this.pos.y)) { 25 | 26 | x=0; 27 | } 28 | 29 | //check the y movement 30 | ptl = createVector(tl.x, tl.y +movement.y); 31 | pbr = createVector(br.x, br.y + movement.y); 32 | if ((ptl.x this.pos.x) &&( ptl.y < this.bottomRight.y && pbr.y > this.pos.y)) { 33 | y=0; 34 | } 35 | 36 | return createVector(x, y); 37 | } 38 | 39 | collision(ptl, pbr) {//player dimensions 40 | //add the x first 41 | 42 | if ((ptl.x this.pos.x) &&( ptl.y < this.bottomRight.y && pbr.y > this.pos.y)) { 43 | return true; 44 | } 45 | return false; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Node.js: -------------------------------------------------------------------------------- 1 | class Node { //used to define short term goals for the players 2 | //------------------------------------------------------------------------------------------------------------------------------ 3 | constructor(nodeTileOrCoin, isTile, isCoin) { 4 | this.reached = false; 5 | this.distToFinish = 0.0; 6 | this.isCoin = isCoin; 7 | 8 | if(isTile){ 9 | this.pos = createVector(nodeTileOrCoin.pixelPos.x, nodeTileOrCoin.pixelPos.y); 10 | this.w = tileSize; 11 | this.h = tileSize; 12 | }else if(isCoin){ 13 | this.pos = createVector(nodeTileOrCoin.pos.x- nodeTileOrCoin.diameter/2.0, nodeTileOrCoin.pos.y- nodeTileOrCoin.diameter/2.0); 14 | this.w = nodeTileOrCoin.diameter; 15 | this.h = nodeTileOrCoin.diameter; 16 | } 17 | this.bottomRight = createVector(this.pos.x + this.w, this.pos.y + this.h); 18 | 19 | } 20 | 21 | //------------------------------------------------------------------------------------------------------------------------------ 22 | collision( ptl, pbr) {//player dimensions 23 | if ((ptl.x this.pos.x) &&( ptl.y < this.bottomRight.y && pbr.y > this.pos.y)) { 24 | this.reached = true; 25 | return true; 26 | }else if(!this.isCoin && pbr.x < this.pos.x){ 27 | this.reached = false; 28 | 29 | } 30 | return false; 31 | } 32 | //------------------------------------------------------------------------------------------------------------------------------ 33 | //set the distance to finish by adding the distance to the finish for the node n plus the distance from this node to node n 34 | setDistanceToFinish(n) { 35 | this.distToFinish = n.distToFinish + dist(this.pos.x, this.pos.y, n.pos.x, n.pos.y); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AdamAI 2 |

3 | An A.I. that learns to traverse a maze using the genetic algorithm. 4 | 5 | Using the genetic algorithm, the A.I. “learns” to go through the maze using the concepts of natural selection and survival of the fittest. The base of the natural selection is firstly a single generation of A.I.s (all randomly selected) and as they go through the maze, the fittest is selected by examining the shortest distance to checkpoint in least amount of moves. By increasing the number of moves available to them every five generations so that they master the moves more efficiently (incremental learning), they are able to ‘remember’ old directions and pass on genes of efficiency to later generations. 6 | 7 | **Note**: It takes about ~50 generations for the A.I. to reach the first checkpoint (when the evolution speed is set to 6), ~120 generations for the A.I. to reach the second, and so forth. The learning takes time but it eventually does win, never missing a checkpoint or dying in the face of enemies. Your own evolution took about 8 million years, so have patience with Adam A.I. 8 |

9 | 10 |

11 |

12 | 13 | ## Features 14 | * **The A.I**: The little square at the start black end is the 'fittest' from the entire generation. To see the generation itself, press SPACE on your keyboard to do so. 15 | * **Evolution Cycle Controls**: You are able to change the population size (more/less chance for a better fit), mutation rate (how much the A.I. deviates from the norm), and evolution speed (just make the process faster; set to 6 if you do not want to wait forever). You are also able to increase a certain number of moves by a certain number of generations (the recommended amount is 5 moves per 5 generations). 16 | * **Play it Yourself**: Press P to play the game yourself. For a human equipped with a mind adapted from years of evolution, this should be easy to you. But once the A.I. beats the game, you'll find that you're more prone to mistakes than it is. 17 | * **Evolution Highlights**: Press G to replay evolution highlights. 18 | 19 | This project was jointly created with a very nice human, [@RaiyanRahman](https://github.com/RaiyanRahman). 20 | 21 | Check out [Code Bullet](https://github.com/Code-Bullet) for a rudimentary basis of the genetic algorithm. 22 | -------------------------------------------------------------------------------- /Brain.js: -------------------------------------------------------------------------------- 1 | class Brain { 2 | constructor(size){ 3 | this.directions = []; 4 | this.step =0; 5 | this.randomize(size); 6 | 7 | } 8 | //-------------------------------------------------------------------------------------------------------------------------------- 9 | //sets all the vectors in directions to a random vector with length 1 10 | randomize(size) { 11 | for (var i = 0; i< size; i++) { 12 | this.directions[i] = this.getRandomDirection(); 13 | } 14 | } 15 | 16 | //--------------------------------------------------------------------------------------------------------------------------------------------------------------- 17 | //returns a random PVector 18 | getRandomDirection() { 19 | var randomNumber = floor(random(9)); 20 | switch(randomNumber) { 21 | case 0: 22 | return createVector(0, 1); 23 | case 1: 24 | return createVector(1, 1); 25 | case 2: 26 | return createVector(1, 0); 27 | case 3: 28 | return createVector(1, -1); 29 | case 4: 30 | return createVector(0, -1); 31 | case 5: 32 | return createVector(-1, -1); 33 | case 6: 34 | return createVector(-1, 0); 35 | case 7: 36 | return createVector(-1, 1); 37 | case 8: 38 | return createVector(0, 0); 39 | } 40 | 41 | return createVector(); 42 | } 43 | 44 | //------------------------------------------------------------------------------------------------------------------------------------- 45 | //returns a perfect copy of this brain object 46 | clone() { 47 | var clone = new Brain(this.directions.length); 48 | for (var i = 0; i < this.directions.length; i++) { 49 | clone.directions[i] = this.directions[i].copy(); 50 | } 51 | return clone; 52 | } 53 | 54 | //---------------------------------------------------------------------------------------------------------------------------------------- 55 | 56 | //mutates the brain by setting some of the directions to random vectors 57 | mutate(died, deathStep) { 58 | //chance that any vector in directions gets changed 59 | for (var i =0; i< this.directions.length; i++) { 60 | var rand = random(1); 61 | if (died && i > deathStep - 10) { 62 | rand = random(0.2); 63 | } 64 | 65 | if (rand < mutationRate) { 66 | //set this direction as a random direction 67 | this.directions[i] = this.getRandomDirection(); 68 | } 69 | } 70 | } 71 | 72 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 73 | //increases the number of elements in directions by 5 74 | increaseMoves() { 75 | for(var i = 0 ; i< increaseMovesBy ;i++){ 76 | this.directions.push(this.getRandomDirection()); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Dot.js: -------------------------------------------------------------------------------- 1 | class Dot { 2 | 3 | constructor( t1, t2, velX) { 4 | this.position = createVector(t1.pixelPos.x + tileSize/2, t1.pixelPos.y + tileSize/2); 5 | this.startingPos = createVector(t1.pixelPos.x + tileSize/2, t1.pixelPos.y + tileSize/2); 6 | this.speed = floor(tileSize/6.6); 7 | this.velocity = createVector(velX*this.speed, 0); 8 | this.startingVel = createVector(velX*this.speed, 0); 9 | this.bouncers = []; 10 | this.bouncers[0] = t1; 11 | this.bouncers[1] = t2; 12 | this.diameter = tileSize/2.0; 13 | this.bounceWait = -1; 14 | this.bounceTimer = 10; 15 | } 16 | 17 | //------------------------------------------------------------------------------------------------------------ 18 | //moves the dot 19 | move() { 20 | 21 | for (var i = 0; i < this.bouncers.length; i++) { 22 | if (this.bounceTimer < 0 && dist(this.position.x, this.position.y, this.bouncers[i].pixelPos.x + tileSize/2, this.bouncers[i].pixelPos.y + tileSize/2) < this.speed) {//if reached bouncer 23 | this.bounceTimer = 10; 24 | this.bounceWait= 1;//wait 1 frames then change direction 25 | } 26 | } 27 | if (this.bounceWait ==0) { 28 | //change direction 29 | this.velocity.x *= -1; 30 | } 31 | 32 | this.position.add(this.velocity);//move dot 33 | this.bounceTimer --; 34 | this.bounceWait --; 35 | } 36 | 37 | //------------------------------------------------------------------------------------------------------------ 38 | //draws the dot 39 | show() { 40 | fill(100,100,100); 41 | stroke(0); 42 | strokeWeight(4); 43 | ellipse(this.position.x, this.position.y, this.diameter, this.diameter); 44 | } 45 | 46 | 47 | //------------------------------------------------------------------------------------------------------------ 48 | //returns true of the Pvectors define a square which collides with this dot 49 | collides(ptl, pbr) {//player dimensions 50 | 51 | var topLeft = createVector(this.position.x - this.diameter/2, this.position.y-this.diameter/2); 52 | var bottomRight = createVector(this.position.x + this.diameter/2, this.position.y + this.diameter/2); 53 | var playerSize = bottomRight.x - topLeft.x; 54 | if ((ptl.x topLeft.x) &&( ptl.y < bottomRight.y && pbr.y > topLeft.y)) { 55 | 56 | if (dist(this.position.x, this.position.y, (ptl.x + pbr.x) /2.0, (ptl.y + pbr.y) /2.0)< this.diameter/2 + sqrt(playerSize*playerSize *2)/2) { 57 | return true; 58 | } 59 | } 60 | return false; 61 | } 62 | //------------------------------------------------------------------------------------------------------------ 63 | //returns the dot to its starting state 64 | 65 | resetDot() { 66 | this.position = this.startingPos.copy(); 67 | this.velocity = this.startingVel.copy(); 68 | this.bounceTimer = 10; 69 | this.bounceWait = -1; 70 | } 71 | //------------------------------------------------------------------------------------------------------------ 72 | //returns a copy of this dot object 73 | clone() { 74 | var clone = new Dot(this.bouncers[0], this.bouncers[1], floor(this.velocity.x)); 75 | clone.velocity = this.velocity.copy(); 76 | clone.position = this.position.copy(); 77 | clone.startingVel = this.startingVel.copy(); 78 | clone.bounceTimer = this.bounceTimer; 79 | clone.bounceWait = this.bounceWait; 80 | return clone; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /setting_level.js: -------------------------------------------------------------------------------- 1 | //functions which setup the level 2 | 3 | function setLevel1Walls() { 4 | for (var i = 0; i < 22; i++) { 5 | for (var j = 0; j < 10; j++) { 6 | if (i >= 2 && i < 20 && j >= 1 && j < 9){ 7 | tiles[i][j].wall = false; 8 | } 9 | else{ 10 | tiles[i][j].wall = true; 11 | } 12 | } 13 | } 14 | } 15 | 16 | function setLevel1Goal() { 17 | tiles[17][1].goal = true; 18 | tiles[18][1].goal = true; 19 | tiles[19][1].goal = true; 20 | tiles[17][2].goal = true; 21 | tiles[18][2].goal = true; 22 | tiles[19][2].goal = true; 23 | } 24 | 25 | function setLevel1SafeArea() { 26 | tiles[2][8].goal = true; 27 | tiles[3][8].goal = true; 28 | tiles[4][8].goal = true; 29 | tiles[2][7].goal = true; 30 | tiles[3][7].goal = true; 31 | tiles[4][7].goal = true; 32 | } 33 | 34 | function setEdges() { 35 | // The Maze walls - The first bottom row. 36 | tiles[2][6].edges.push(2); 37 | tiles[3][6].edges.push(2); 38 | tiles[4][6].edges.push(2); 39 | tiles[5][6].edges.push(2); 40 | tiles[6][6].edges.push(2); 41 | tiles[7][6].edges.push(2); 42 | tiles[8][6].edges.push(2); 43 | tiles[9][6].edges.push(2); 44 | tiles[10][6].edges.push(2); 45 | tiles[11][6].edges.push(2); 46 | tiles[12][6].edges.push(2); 47 | tiles[13][6].edges.push(2); 48 | tiles[14][6].edges.push(2); 49 | tiles[15][6].edges.push(2); 50 | tiles[16][6].edges.push(2); 51 | // The Maze walls - The second bottom row. 52 | tiles[13][5].edges.push(2); 53 | tiles[14][5].edges.push(2); 54 | tiles[15][5].edges.push(2); 55 | tiles[16][5].edges.push(2); 56 | tiles[7][4].edges.push(2); 57 | tiles[8][4].edges.push(2); 58 | tiles[9][4].edges.push(2); 59 | tiles[4][4].edges.push(2); 60 | tiles[3][4].edges.push(2); 61 | tiles[2][4].edges.push(2); 62 | // Lil loopies. 63 | tiles[10][4].edges.push(1); 64 | tiles[11][3].edges.push(2); 65 | tiles[11][4].edges.push(1); 66 | tiles[11][5].edges.push(1); 67 | tiles[10][5].edges.push(1); 68 | tiles[10][6].edges.push(1); 69 | // The Maze walls - The third bottom row. 70 | tiles[6][3].edges.push(2); 71 | tiles[7][3].edges.push(2); 72 | tiles[8][3].edges.push(2); 73 | tiles[9][3].edges.push(2); 74 | tiles[4][3].edges.push(2); 75 | tiles[3][3].edges.push(2); 76 | tiles[13][3].edges.push(2); 77 | tiles[14][3].edges.push(2); 78 | tiles[15][3].edges.push(2); 79 | tiles[16][3].edges.push(2); 80 | tiles[17][3].edges.push(2); 81 | tiles[18][3].edges.push(2); 82 | tiles[19][3].edges.push(2); 83 | // Lil loopies! 84 | tiles[4][3].edges.push(1); 85 | tiles[9][3].edges.push(1); 86 | tiles[9][4].edges.push(1); 87 | tiles[12][3].edges.push(1); 88 | // The Maze walls - The fourth bottom row. 89 | tiles[5][2].edges.push(2); 90 | tiles[6][2].edges.push(2); 91 | tiles[7][2].edges.push(2); 92 | tiles[8][2].edges.push(2); 93 | tiles[9][2].edges.push(2); 94 | tiles[10][2].edges.push(2); 95 | tiles[11][2].edges.push(2); 96 | tiles[12][2].edges.push(2); 97 | // The Maze walls - The fifth bottom row. 98 | tiles[2][1].edges.push(2); 99 | tiles[3][1].edges.push(1); 100 | tiles[3][2].edges.push(1); 101 | tiles[3][2].edges.push(2); 102 | tiles[6][1].edges.push(1); 103 | tiles[8][1].edges.push(1); 104 | tiles[10][1].edges.push(1); 105 | tiles[14][1].edges.push(1); 106 | // The outer walls. 107 | for (var i = 1; i< 21; i++) { 108 | for (var j = 1; j< 9; j++) { 109 | console.log('i: ' + i + ' j: ' + j) 110 | if (!tiles[i][j].wall) { 111 | if (tiles[i+1][j].wall) { 112 | tiles[i][j].edges.push(1); 113 | } 114 | if (tiles[i][j+1].wall) { 115 | tiles[i][j].edges.push(2); 116 | } 117 | if (tiles[i-1][j].wall) { 118 | tiles[i][j].edges.push(3); 119 | } 120 | if (tiles[i][j-1].wall) { 121 | tiles[i][j].edges.push(4); 122 | } 123 | } 124 | } 125 | } 126 | } 127 | 128 | 129 | function setDots() { 130 | dots.push(new Dot(tiles[2][5], tiles[10][5], 0.6)); 131 | dots.push(new Dot(tiles[4][2], tiles[15][2], 1)); 132 | // dots.push(new Dot(tiles[12][4], tiles[19][4], 0.3)); 133 | dots.push(new Dot(tiles[19][5], tiles[12][5], -0.3)); 134 | } 135 | 136 | 137 | function setSolids() { 138 | solids.push(new Solid(tiles[2][1], tiles[2][9])); // Left boundary. 139 | solids.push(new Solid(tiles[1][1], tiles[20][1])); // Top boundary. 140 | solids.push(new Solid(tiles[1][9], tiles[20][9])); // Bottom boundary. 141 | solids.push(new Solid(tiles[20][1], tiles[20][9])); // Right boundary. 142 | // The vertical inner maze walls. 143 | solids.push(new Solid(tiles[4][1], tiles[4][3])); 144 | solids.push(new Solid(tiles[5][3], tiles[5][4])); 145 | solids.push(new Solid(tiles[7][1], tiles[7][2])); 146 | solids.push(new Solid(tiles[9][1], tiles[9][2])); 147 | solids.push(new Solid(tiles[10][3], tiles[10][5])); 148 | solids.push(new Solid(tiles[11][4], tiles[11][7])); 149 | solids.push(new Solid(tiles[12][4], tiles[12][6])); 150 | solids.push(new Solid(tiles[11][1], tiles[11][2])); 151 | solids.push(new Solid(tiles[13][3], tiles[13][4])); 152 | solids.push(new Solid(tiles[15][1], tiles[15][2])); 153 | // The horizontal inner maze walls. 154 | solids.push(new Solid(tiles[2][2], tiles[3][2])); 155 | solids.push(new Solid(tiles[3][3], tiles[4][3])); 156 | solids.push(new Solid(tiles[5][3], tiles[13][3])); 157 | solids.push(new Solid(tiles[3][4], tiles[5][4])); 158 | solids.push(new Solid(tiles[6][4], tiles[10][4])); 159 | solids.push(new Solid(tiles[11][4], tiles[12][4])); 160 | solids.push(new Solid(tiles[13][4], tiles[20][4])); 161 | solids.push(new Solid(tiles[2][5], tiles[5][5])); 162 | solids.push(new Solid(tiles[7][5], tiles[10][5])); 163 | solids.push(new Solid(tiles[13][6], tiles[17][6])); 164 | solids.push(new Solid(tiles[2][7], tiles[17][7])); 165 | } 166 | -------------------------------------------------------------------------------- /Population.js: -------------------------------------------------------------------------------- 1 | class Population { 2 | 3 | constructor(size) { 4 | this.players = []; 5 | this.fitnessSum = 0.0; 6 | this.gen = 1; 7 | this.bestPlayer =0; 8 | this.minStep = 10000; 9 | this.genPlayers = []; 10 | this.bestFitness = 0; 11 | this.solutionFound = false; 12 | 13 | for (var i = 0; i< size; i++) { 14 | this.players[i] = new Player(); 15 | } 16 | } 17 | 18 | 19 | //------------------------------------------------------------------------------------------------------------------------------ 20 | //show all players 21 | show() { 22 | if (!showBest) { 23 | for (var i = 1; i< this.players.length; i++) { 24 | this.players[i].show(); 25 | } 26 | } 27 | this.players[0].show(); 28 | } 29 | 30 | //------------------------------------------------------------------------------------------------------------------------------- 31 | //update all players 32 | update() { 33 | for (var i = 0; i< this.players.length; i++) { 34 | if (this.players[i].brain.step > this.minStep) {//if the player has already taken more steps than the best player has taken to reach the goal 35 | this.players[i].dead = true;//then it dead 36 | } else { 37 | this.players[i].update(); 38 | } 39 | } 40 | } 41 | 42 | //----------------------------------------------------------------------------------------------------------------------------------- 43 | //calculate all the fitnesses 44 | calculateFitness() { 45 | for (var i = 0; i< this.players.length; i++) { 46 | this.players[i].calculateFitness(); 47 | } 48 | } 49 | 50 | 51 | //------------------------------------------------------------------------------------------------------------------------------------ 52 | //returns whether all the players are either dead or have reached the goal 53 | allPlayersDead() { 54 | for (var i = 0; i< this.players.length; i++) { 55 | if (!this.players[i].dead && !this.players[i].reachedGoal) { 56 | return false; 57 | } 58 | } 59 | print("bah:"); 60 | return true; 61 | } 62 | 63 | 64 | 65 | //------------------------------------------------------------------------------------------------------------------------------------- 66 | 67 | //gets the next generation of players 68 | naturalSelection() { 69 | var newPlayers = [];//next gen 70 | this.setBestPlayer(); 71 | this.calculateFitnessSum(); 72 | 73 | //the champion lives on 74 | newPlayers[0] = this.players[this.bestPlayer].gimmeBaby(); 75 | newPlayers[0].isBest = true; 76 | for (var i = 1; i< populationSize; i++) { 77 | //select parent based on fitness 78 | var parent = this.selectParent(); 79 | 80 | //get baby from them 81 | newPlayers[i] = parent.gimmeBaby(); 82 | } 83 | 84 | // this.players = newPlayers.slice(); 85 | this.players = []; 86 | for(var i = 0 ; i< newPlayers.length;i++){ 87 | this.players[i] = newPlayers[i]; 88 | } 89 | this.gen ++; 90 | } 91 | 92 | 93 | //-------------------------------------------------------------------------------------------------------------------------------------- 94 | //you get it 95 | calculateFitnessSum() { 96 | this.fitnessSum = 0; 97 | for (var i = 0; i< this.players.length; i++) { 98 | this.fitnessSum += this.players[i].fitness; 99 | } 100 | } 101 | 102 | //------------------------------------------------------------------------------------------------------------------------------------- 103 | 104 | //chooses player from the population to return randomly(considering fitness) 105 | 106 | //this function works by randomly choosing a value between 0 and the sum of all the fitnesses 107 | //then go through all the players and add their fitness to a running sum and if that sum is greater than the random value generated that player is chosen 108 | //since players with a higher fitness function add more to the running sum then they have a higher chance of being chosen 109 | selectParent() { 110 | var rand = random(this.fitnessSum); 111 | 112 | 113 | var runningSum = 0; 114 | 115 | for (var i = 0; i< this.players.length; i++) { 116 | runningSum+= this.players[i].fitness; 117 | if (runningSum > rand) { 118 | return this.players[i]; 119 | } 120 | } 121 | 122 | //should never get to this point 123 | 124 | return null; 125 | } 126 | 127 | //------------------------------------------------------------------------------------------------------------------------------------------ 128 | //mutates all the brains of the babies 129 | mutateDemBabies() { 130 | for (var i = 1; i< this.players.length; i++) { 131 | this.players[i].brain.mutate(this.players[i].deathByDot, this.players[i].deathAtStep); 132 | this.players[i].deathByDot = false; 133 | this.players[i].gen = this.gen; 134 | } 135 | this.players[0].deathByDot = false; 136 | this.players[0].gen = this.gen; 137 | } 138 | 139 | //--------------------------------------------------------------------------------------------------------------------------------------------- 140 | //finds the player with the highest fitness and sets it as the best player 141 | setBestPlayer() { 142 | var max = 0; 143 | var maxIndex = 0; 144 | for (var i = 0; i< this.players.length; i++) { 145 | if (this.players[i].fitness > max) { 146 | max = this.players[i].fitness; 147 | maxIndex = i; 148 | } 149 | } 150 | 151 | this.bestPlayer = maxIndex; 152 | 153 | if (max > this.bestFitness) { 154 | this.bestFitness = max; 155 | this.genPlayers.push(this.players[this.bestPlayer].gimmeBaby()); 156 | } 157 | 158 | //if this player reached the goal then reset the minimum number of steps it takes to get to the goal 159 | if (this.players[this.bestPlayer].reachedGoal) { 160 | this.minStep = this.players[this.bestPlayer].brain.step; 161 | this.solutionFound = true; 162 | } 163 | } 164 | 165 | 166 | increaseMoves() { 167 | if (this.players[0].brain.directions.length < 10000000 && !this.solutionFound) { 168 | for (var i = 0; i< this.players.length; i++) { 169 | this.players[i].brain.increaseMoves(); 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Player.js: -------------------------------------------------------------------------------- 1 | class Player{ 2 | constructor(){ 3 | this.pos = createVector(3*tileSize + xoff,8* tileSize + yoff); 4 | this.vel = createVector(0,0); 5 | this.size = tileSize/2.0; 6 | this.playerSpeed = tileSize/15.0; 7 | this.dead = false; 8 | this.reachedGoal = false; 9 | this.fadeCounter = 255; 10 | this.isBest = false; 11 | this.deathByDot = false; 12 | this.deathAtStep = 0; 13 | this.moveCount = 0; 14 | this.gen =1; 15 | this.fitness = 0; 16 | this.nodes = []; 17 | this.fading = false; 18 | this.brain = new Brain(numberOfSteps); 19 | this.human = false; 20 | this.coins = [ 21 | new Coin(18.5*tileSize+xoff, 7*tileSize+yoff), 22 | new Coin(10.5*tileSize+xoff, 3.5*tileSize+yoff), 23 | new Coin(7*tileSize+xoff, 6*tileSize+yoff), 24 | new Coin(6*tileSize+xoff, 5*tileSize+yoff), 25 | new Coin(2.5*tileSize+xoff, 3.5*tileSize+yoff), 26 | new Coin(4.5*tileSize+xoff, 3.5*tileSize+yoff), 27 | new Coin(4.5*tileSize+xoff, 2.5*tileSize+yoff), 28 | ]; 29 | this.setNodes(); 30 | 31 | } 32 | 33 | setNodes() { 34 | this.nodes[0] = new Node(this.coins[0],false,true); 35 | this.nodes[1] = new Node(this.coins[1],false,true); 36 | this.nodes[2] = new Node(this.coins[2],false,true); 37 | this.nodes[3] = new Node(this.coins[3],false,true); 38 | this.nodes[4] = new Node(this.coins[4],false,true); 39 | this.nodes[5] = new Node(this.coins[5],false,true); 40 | this.nodes[6] = new Node(this.coins[6],false,true); 41 | this.nodes[7] = new Node(tiles[19][2],true,false); 42 | this.nodes[6].setDistanceToFinish(this.nodes[7]); 43 | this.nodes[5].setDistanceToFinish(this.nodes[6]); 44 | this.nodes[4].setDistanceToFinish(this.nodes[5]); 45 | this.nodes[3].setDistanceToFinish(this.nodes[4]); 46 | this.nodes[2].setDistanceToFinish(this.nodes[3]); 47 | this.nodes[1].setDistanceToFinish(this.nodes[2]); 48 | this.nodes[0].setDistanceToFinish(this.nodes[1]); 49 | } 50 | 51 | show(){ 52 | fill(230, 230, 255, this.fadeCounter); 53 | if (this.isBest && !showBest) { 54 | fill(255, 0, 0, this.fadeCounter); 55 | } 56 | stroke(0, 0, 0, this.fadeCounter); 57 | strokeWeight(4); 58 | rect(this.pos.x, this.pos.y, this.size, this.size); 59 | stroke(0); 60 | for (var coinNum = 0; coinNum < this.coins.length; coinNum++) { 61 | this.coins[coinNum].show(); 62 | } 63 | } 64 | 65 | move(){ 66 | if (!humanPlaying){ 67 | if (this.moveCount == 0) {//move in the direction for 6 frames 68 | if (this.brain.directions.length > this.brain.step) {//if there are still directions left then set the velocity as the next PVector in the direcitons array 69 | this.vel = this.brain.directions[this.brain.step]; 70 | this.brain.step++; 71 | } else {//if at the end of the directions array then the player is dead 72 | this.dead = true; 73 | this.fading = true; 74 | } 75 | this.moveCount =6; 76 | } else { 77 | this.moveCount--; 78 | } 79 | } 80 | var temp = createVector(this.vel.x, this.vel.y); 81 | temp.normalize(); 82 | temp.mult(this.playerSpeed); 83 | for (var i = 0; i< solids.length; i++) { 84 | temp = solids[i].restrictMovement(this.pos, createVector(this.pos.x+this.size, this.pos.y+this.size), temp); 85 | } 86 | this.pos.add(temp); 87 | 88 | } 89 | 90 | //checks if the player 91 | checkCollisions() { 92 | for (var coinNum = 0; coinNum < this.coins.length; coinNum++) { 93 | this.coins[coinNum].collides(this.pos, createVector(this.pos.x+this.size, this.pos.y+this.size)); 94 | } 95 | for (var i = 0; i< dots.length; i++) { 96 | if (dots[i].collides(this.pos, createVector(this.pos.x+this.size, this.pos.y+this.size))) { 97 | this.fading = true; 98 | this.dead = true; 99 | this.deathByDot = true; 100 | this.deathAtStep = this.brain.step; 101 | } 102 | } 103 | if (this.coins[0].taken && this.coins[1].taken && this.coins[2].taken && this.coins[3].taken && 104 | this.coins[4].taken && this.coins[5].taken && this.coins[6].taken && 105 | winArea.collision(this.pos, createVector(this.pos.x+this.size, this.pos.y+this.size))) { 106 | this.reachedGoal = true; 107 | } 108 | for (var i = 0; i< this.nodes.length; i++) { 109 | this.nodes[i].collision(this.pos, createVector(this.pos.x+this.size, this.pos.y+this.size)); 110 | } 111 | } 112 | //---------------------------------------------------------------------------------------------------------------------------------------------------------- 113 | update() { 114 | if (!this.dead && !this.reachedGoal) { 115 | this.move(); 116 | this.checkCollisions(); 117 | } else if (this.fading) { 118 | if (this.fadeCounter > 0) { 119 | if(humanPlaying || replayGens){ 120 | this.fadeCounter -=10; 121 | }else{ 122 | this.fadeCounter = 0; 123 | 124 | } 125 | } 126 | } 127 | } 128 | //---------------------------------------------------------------------------------------------------------------------------------------------------------- 129 | 130 | calculateFitness() { 131 | if (this.reachedGoal) {//if the dot reached the goal then the fitness is based on the amount of steps it took to get there 132 | this.fitness = 1.0/16.0 + 10000.0/(this.brain.step * this.brain.step); 133 | } else {//if the dot didn't reach the goal then the fitness is based on how close it is to the goal 134 | var estimatedDistance = 0.0;//the estimated distance of the path from the player to the goal 135 | for (var i = this.nodes.length-1; i>=0; i--) { 136 | if (!this.nodes[i].reached) { 137 | estimatedDistance = this.nodes[i].distToFinish; 138 | estimatedDistance += dist(this.pos.x, this.pos.y, this.nodes[i].pos.x, this.nodes[i].pos.y); 139 | } 140 | } 141 | if (this.deathByDot) { 142 | estimatedDistance *= 0.9; 143 | } 144 | this.fitness = 1.0/(estimatedDistance * estimatedDistance); 145 | } 146 | this.fitness*=this.fitness; 147 | if(this.coins[0].taken || this.coins[1].taken || this.coins[2].taken || this.coins[3].taken || 148 | this.coins[4].taken || this.coins[5].taken || this.coins[6].taken){ 149 | this.fitness *=1.2; 150 | } 151 | } 152 | 153 | 154 | 155 | //---------------------------------------------------------------------------------------------------------------------------------------------------------- 156 | gimmeBaby() { 157 | var baby = new Player(); 158 | baby.brain = this.brain.clone();//babies have the same brain as their parents 159 | baby.deathByDot = this.deathByDot; 160 | baby.deathAtStep = this.deathAtStep; 161 | baby.gen = this.gen; 162 | return baby; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /sketch.js: -------------------------------------------------------------------------------- 1 | var tileSize = 50; 2 | var xoff = 145; 3 | var yoff = 100; 4 | 5 | //human playing vars 6 | var humanPlaying = false; 7 | var left = false; 8 | var right = false; 9 | var up = false; 10 | var down = false; 11 | var p; 12 | 13 | //arrays 14 | var tiles = []; 15 | var solids = []; 16 | var dots = []; 17 | var savedDots = []; 18 | 19 | var showBest = true; 20 | 21 | var winArea;//a solid which is the win zone i.e. the green bits 22 | 23 | //gen replay vars 24 | var replayGens = false; 25 | var genPlayer; 26 | var upToGenPos = 0; 27 | 28 | //population vars 29 | var numberOfSteps = 10; 30 | var testPopulation; 31 | 32 | var winCounter = -1; 33 | 34 | var img; 35 | var flip = true; 36 | 37 | //population size vars 38 | var populationSize = 500; 39 | var popPara; 40 | var popPlus; 41 | var popMinus; 42 | 43 | //mutation rate vars 44 | var mutationRate = 0.04; 45 | var mrPara; 46 | var mrPlus; 47 | var mrMinus; 48 | 49 | //evolution speed vars 50 | var evolutionSpeed =1; 51 | var speedPara; 52 | var speedPlus; 53 | var speedMinus; 54 | 55 | //increaseMoves 56 | var movesH3; 57 | 58 | var increaseMovesBy =5; 59 | var movesPara; 60 | var movesPlus; 61 | var movesMinus; 62 | 63 | var increaseEvery =5; 64 | var everyPara; 65 | var everyPlus; 66 | var everyMinus; 67 | 68 | function setup() { 69 | var canvas = createCanvas(1200,640); 70 | htmlStuff(); 71 | for (var i = 0; i< 22; i++) { 72 | tiles[i] = []; 73 | for (var j = 0; j< 10; j++) { 74 | tiles[i][j] = new Tile(i, j); 75 | } 76 | } 77 | 78 | setLevel1Walls(); 79 | setLevel1Goal(); 80 | setLevel1SafeArea(); 81 | setEdges(); 82 | // setMazeWalls(); 83 | setSolids(); 84 | 85 | p = new Player(); 86 | setDots(); 87 | winArea = new Solid(tiles[17][1], tiles[20][2]); 88 | testPopulation = new Population(populationSize); 89 | img = loadImage("https://i.imgur.com/QZf0d6r.gif"); 90 | 91 | //prevents the window from moving from the arrow keys or the spacebar 92 | window.addEventListener("keydown", function(e) { 93 | // space and arrow keys 94 | if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) { 95 | e.preventDefault(); 96 | } 97 | }, false); 98 | } 99 | var showedCoin = false; 100 | function draw(){ 101 | showedCoin = false; 102 | background(230,230,255); 103 | drawTiles(); 104 | writeShit(); 105 | 106 | 107 | if (humanPlaying) {//if the user is controlling the square 108 | if ((p.dead && p.fadeCounter<=0) || p.reachedGoal) { 109 | //reset player and dots 110 | if(p.reachedGoal){ 111 | winCounter = 100; 112 | 113 | } 114 | p = new Player(); 115 | p.human = true; 116 | resetDots(); 117 | 118 | } else { 119 | //update the dots and the players and show them to the screen 120 | 121 | 122 | moveAndShowDots(); 123 | 124 | p.update(); 125 | p.show(); 126 | } 127 | } else 128 | if (replayGens) {//if replaying the best generations 129 | if ((genPlayer.dead && genPlayer.fadeCounter <=0) || genPlayer.reachedGoal) { //if the current gen is done 130 | upToGenPos ++;//next gen 131 | if (testPopulation.genPlayers.length <= upToGenPos) {//if reached the final gen 132 | //stop replaying gens 133 | upToGenPos = 0; 134 | replayGens = false; 135 | //return the dots to their saved position 136 | 137 | loadDots(); 138 | } else {//if there are more generations to show 139 | //set gen player as the best player of that generation 140 | genPlayer = testPopulation.genPlayers[upToGenPos].gimmeBaby(); 141 | //reset the dots positions 142 | resetDots(); 143 | } 144 | } else {//if not done 145 | //move and show dots 146 | moveAndShowDots(); 147 | //move and update player 148 | genPlayer.update(); 149 | genPlayer.show(); 150 | } 151 | } else//if training normaly 152 | if (testPopulation.allPlayersDead()) { 153 | //genetic algorithm 154 | testPopulation.calculateFitness(); 155 | testPopulation.naturalSelection(); 156 | testPopulation.mutateDemBabies(); 157 | //reset dots 158 | resetDots(); 159 | 160 | //every 5 generations incease the number of moves by 5 161 | if (testPopulation.gen % increaseEvery ==0) { 162 | testPopulation.increaseMoves(); 163 | } 164 | 165 | } else { 166 | 167 | // moveAndShowDots(); 168 | //update and show population 169 | 170 | for(var j = 0 ; j< evolutionSpeed; j++){ 171 | for (var i = 0; i < dots.length; i ++) { 172 | dots[i].move(); 173 | } 174 | testPopulation.update(); 175 | } 176 | 177 | for (var i = 0; i < dots.length; i ++) { 178 | dots[i].show(); 179 | } 180 | testPopulation.show(); 181 | } 182 | 183 | } 184 | function moveAndShowDots(){ 185 | for (var i = 0; i < dots.length; i ++) { 186 | dots[i].move(); 187 | dots[i].show(); 188 | } 189 | 190 | } 191 | function resetDots(){ 192 | for (var i = 0; i < dots.length; i ++) { 193 | dots[i].resetDot(); 194 | } 195 | 196 | } 197 | function drawTiles(){ 198 | for (var i = 0; i< tiles.length; i++) { 199 | for (var j = 0; j< tiles[0].length; j++) { 200 | tiles[i][j].show(); 201 | } 202 | } 203 | for (var i = 0; i< tiles.length; i++) { 204 | for (var j = 0; j< tiles[0].length; j++) { 205 | tiles[i][j].showEdges(); 206 | } 207 | } 208 | } 209 | 210 | function loadDots(){ 211 | for (var i = 0; i< dots.length; i++) { 212 | dots[i] = savedDots[i].clone(); 213 | } 214 | } 215 | 216 | function saveDots(){ 217 | for (var i = 0; i< dots.length; i++) { 218 | savedDots[i] = dots[i].clone(); 219 | } 220 | } 221 | 222 | function writeShit(){ 223 | 224 | fill(0, 0, 0); 225 | textSize(18); 226 | noStroke(); 227 | text("Press SPACE to show the generation that the A.I. belongs to.", 440,590).fo; 228 | text(" \tPress P to play the game yourself. \t\t\t Press G to replay evolution highlights.",370,620 ); 229 | textSize(20); 230 | if(winCounter > 0){ 231 | 232 | if(flip){ 233 | push(); 234 | 235 | scale(-1.0,1.0); 236 | image(img,-300 -img.width + random(5),100+ random(5)); 237 | pop(); 238 | }else{ 239 | image(img,300+ random(5),100 + random(5)); 240 | } 241 | textSize(100); 242 | stroke(0); 243 | 244 | winCounter --; 245 | if(winCounter % 10 ==0){ 246 | 247 | flip = !flip; 248 | } 249 | textSize(36); 250 | noStroke(); 251 | } 252 | if (replayGens) { 253 | text("Generation: " + genPlayer.gen, 240, 120); 254 | text("Number of moves: " + genPlayer.brain.directions.length, 950, 120); 255 | } else if(!humanPlaying) { 256 | text("Generation: " + testPopulation.gen, 240, 120); 257 | if(testPopulation.solutionFound){ 258 | text("Wins in " + testPopulation.minStep + " moves",950, 120); 259 | }else{ 260 | text("Number of moves: " + testPopulation.players[0].brain.directions.length, 950, 120); 261 | } 262 | }else{ 263 | text("Solo Gameplay", 620,130); 264 | } 265 | } 266 | function keyPressed(){ 267 | if(humanPlaying){ 268 | switch(keyCode) { 269 | case UP_ARROW: 270 | up = true; 271 | break; 272 | case DOWN_ARROW: 273 | down = true; 274 | break; 275 | case RIGHT_ARROW: 276 | right = true; 277 | break; 278 | case LEFT_ARROW: 279 | left = true; 280 | break; 281 | } 282 | switch(key){ 283 | case 'W': 284 | up = true; 285 | break; 286 | case 'S': 287 | down = true; 288 | break; 289 | case 'D': 290 | right = true; 291 | break; 292 | case 'A': 293 | left = true; 294 | break; 295 | } 296 | setPlayerVelocity(); 297 | }else{//if human is not playing 298 | switch(key) { 299 | case ' ': 300 | showBest = !showBest; 301 | break; 302 | case 'G'://replay gens 303 | if (replayGens) { 304 | upToGenPos = 0; 305 | replayGens = false; 306 | loadDots(); 307 | } else 308 | if (testPopulation.genPlayers.length > 0) { 309 | replayGens = true; 310 | genPlayer = testPopulation.genPlayers[0].gimmeBaby(); 311 | saveDots(); 312 | resetDots(); 313 | } 314 | break; 315 | } 316 | } 317 | 318 | if(key == 'P'){ 319 | if (humanPlaying) {//if human is currently playing 320 | 321 | //reset dots to position 322 | humanPlaying = false; 323 | loadDots(); 324 | } else {//if AI is currently playing 325 | if (replayGens) { 326 | upToGenPos = 0; 327 | replayGens = false; 328 | } 329 | humanPlaying = true; 330 | p = new Player(); 331 | p.human = true; 332 | //save the positions of the dots 333 | saveDots(); 334 | resetDots(); 335 | } 336 | } 337 | } 338 | 339 | 340 | function keyReleased(){ 341 | if(humanPlaying){ 342 | switch(keyCode) { 343 | case UP_ARROW: 344 | up = false; 345 | break; 346 | case DOWN_ARROW: 347 | down = false; 348 | break; 349 | case RIGHT_ARROW: 350 | right = false; 351 | break; 352 | case LEFT_ARROW: 353 | left = false; 354 | break; 355 | } 356 | switch(key){ 357 | case 'W': 358 | up = false; 359 | break; 360 | case 'S': 361 | down = false; 362 | break; 363 | case 'D': 364 | right = false; 365 | break; 366 | case 'A': 367 | left = false; 368 | break; 369 | } 370 | 371 | setPlayerVelocity(); 372 | } 373 | 374 | } 375 | //set the velocity of the player based on what keys are currently down 376 | 377 | function setPlayerVelocity(){ 378 | p.vel.y= 0; 379 | if (up) { 380 | p.vel.y -=1; 381 | } 382 | if (down) { 383 | p.vel.y +=1; 384 | } 385 | p.vel.x= 0; 386 | if (left) { 387 | p.vel.x -=1; 388 | } 389 | if (right) { 390 | p.vel.x +=1; 391 | } 392 | 393 | } 394 | //--------------------------------------------------------------------------------------------------------------------- 395 | function htmlStuff(){ 396 | createElement("h2", "Change the Evolution Cycle") 397 | popPara = createDiv("Population Size: " + populationSize); 398 | popMinus = createButton("-"); 399 | popPlus = createButton('+'); 400 | 401 | popPlus.mousePressed(plusPopSize); 402 | popMinus.mousePressed(minusPopSize); 403 | 404 | mrPara = createDiv("Mutation Rate: " + mutationRate); 405 | mrMinus = createButton("1/2"); 406 | mrPlus = createButton('x2'); 407 | mrPlus.mousePressed(plusmr); 408 | mrMinus.mousePressed(minusmr); 409 | 410 | speedPara = createDiv("Evolution Speed: " + evolutionSpeed); 411 | speedMinus = createButton("-"); 412 | speedPlus = createButton('+'); 413 | speedPlus.mousePressed(plusSpeed); 414 | speedMinus.mousePressed(minusSpeed); 415 | 416 | movesH3 = createElement("h4", "Increase number of player moves by " + increaseMovesBy + " every " + increaseEvery + " generations:"); 417 | movesPara = createDiv("Increase moves by: " + increaseMovesBy); 418 | movesMinus = createButton("-"); 419 | movesPlus = createButton('+'); 420 | movesPlus.mousePressed(plusMoves); 421 | movesMinus.mousePressed(minusMoves); 422 | everyPara = createDiv("Increase every " + increaseEvery + " generations"); 423 | everyMinus = createButton("-"); 424 | everyPlus = createButton('+'); 425 | everyPlus.mousePressed(plusEvery); 426 | everyMinus.mousePressed(minusEvery); 427 | } 428 | 429 | function minusPopSize(){ 430 | if(populationSize > 100){ 431 | populationSize -=100; 432 | popPara.html("Population Size: " + populationSize); 433 | } 434 | } 435 | function plusPopSize(){ 436 | if(populationSize < 10000){ 437 | populationSize +=100; 438 | popPara.html("Population Size: " + populationSize); 439 | 440 | } 441 | } 442 | 443 | function minusmr(){ 444 | if(mutationRate > 0.0001){ 445 | mutationRate /= 2.0; 446 | mrPara.html("Mutation Rate: " + mutationRate); 447 | } 448 | } 449 | function plusmr(){ 450 | if(mutationRate <= 0.5){ 451 | mutationRate *= 2.0; 452 | mrPara.html("Mutation Rate: " + mutationRate); 453 | 454 | } 455 | } 456 | 457 | function minusSpeed(){ 458 | if(evolutionSpeed > 1){ 459 | evolutionSpeed -= 1; 460 | speedPara.html("Evolution Player Speed: " + evolutionSpeed); 461 | } 462 | } 463 | function plusSpeed(){ 464 | if(evolutionSpeed <= 7){ 465 | evolutionSpeed += 1; 466 | speedPara.html("Evolution Player Speed: " + evolutionSpeed); 467 | 468 | } 469 | } 470 | 471 | 472 | function minusMoves(){ 473 | if(increaseMovesBy >= 1){ 474 | increaseMovesBy -= 1; 475 | movesPara.html("Increase moves by: " + increaseMovesBy); 476 | movesH3.html("Increase number of player moves by " + increaseMovesBy + " every " + increaseEvery + " generations"); 477 | } 478 | } 479 | function plusMoves(){ 480 | if(increaseMovesBy <= 500){ 481 | increaseMovesBy += 1; 482 | movesPara.html("Increase moves by: " + increaseMovesBy); 483 | movesH3.html("Increase number of player moves by " + increaseMovesBy + " every " + increaseEvery + " generations"); 484 | } 485 | } 486 | 487 | function minusEvery(){ 488 | if(increaseEvery > 1){ 489 | increaseEvery -= 1; 490 | everyPara.html("Increase every " + increaseEvery + " generations"); 491 | movesH3.html("Increase number of player moves by " + increaseMovesBy + " every " + increaseEvery + " generations"); 492 | } 493 | } 494 | function plusEvery(){ 495 | if(increaseEvery <= 100){ 496 | increaseEvery += 1; 497 | everyPara.html("Increase every " + increaseEvery + " generations"); 498 | movesH3.html("Increase number of player moves by " + increaseMovesBy + " every " + increaseEvery + " generations"); 499 | } 500 | } 501 | -------------------------------------------------------------------------------- /libraries/p5.dom.js: -------------------------------------------------------------------------------- 1 | /*! p5.dom.js v0.3.2 March 25, 2017 */ 2 | /** 3 | *

The web is much more than just canvas and p5.dom makes it easy to interact 4 | * with other HTML5 objects, including text, hyperlink, image, input, video, 5 | * audio, and webcam.

6 | *

There is a set of creation methods, DOM manipulation methods, and 7 | * an extended p5.Element that supports a range of HTML elements. See the 8 | * 9 | * beyond the canvas tutorial for a full overview of how this addon works. 10 | * 11 | *

Methods and properties shown in black are part of the p5.js core, items in 12 | * blue are part of the p5.dom library. You will need to include an extra file 13 | * in order to access the blue functions. See the 14 | * using a library 15 | * section for information on how to include this library. p5.dom comes with 16 | * p5 complete or you can download the single file 17 | * 18 | * here.

19 | *

See tutorial: beyond the canvas 20 | * for more info on how to use this libary. 21 | * 22 | * @module p5.dom 23 | * @submodule p5.dom 24 | * @for p5.dom 25 | * @main 26 | */ 27 | 28 | (function (root, factory) { 29 | if (typeof define === 'function' && define.amd) 30 | define('p5.dom', ['p5'], function (p5) { (factory(p5));}); 31 | else if (typeof exports === 'object') 32 | factory(require('../p5')); 33 | else 34 | factory(root['p5']); 35 | }(this, function (p5) { 36 | 37 | // ============================================================================= 38 | // p5 additions 39 | // ============================================================================= 40 | 41 | /** 42 | * Searches the page for an element with the given ID, class, or tag name (using the '#' or '.' 43 | * prefixes to specify an ID or class respectively, and none for a tag) and returns it as 44 | * a p5.Element. If a class or tag name is given with more than 1 element, 45 | * only the first element will be returned. 46 | * The DOM node itself can be accessed with .elt. 47 | * Returns null if none found. You can also specify a container to search within. 48 | * 49 | * @method select 50 | * @param {String} name id, class, or tag name of element to search for 51 | * @param {String} [container] id, p5.Element, or HTML element to search within 52 | * @return {Object|p5.Element|Null} p5.Element containing node found 53 | * @example 54 | *

55 | * function setup() { 56 | * createCanvas(100,100); 57 | * //translates canvas 50px down 58 | * select('canvas').position(100, 100); 59 | * } 60 | *
61 | *
62 | * // these are all valid calls to select() 63 | * var a = select('#moo'); 64 | * var b = select('#blah', '#myContainer'); 65 | * var c = select('#foo', b); 66 | * var d = document.getElementById('beep'); 67 | * var e = select('p', d); 68 | *
69 | * 70 | */ 71 | p5.prototype.select = function (e, p) { 72 | var res = null; 73 | var container = getContainer(p); 74 | if (e[0] === '.'){ 75 | e = e.slice(1); 76 | res = container.getElementsByClassName(e); 77 | if (res.length) { 78 | res = res[0]; 79 | } else { 80 | res = null; 81 | } 82 | }else if (e[0] === '#'){ 83 | e = e.slice(1); 84 | res = container.getElementById(e); 85 | }else { 86 | res = container.getElementsByTagName(e); 87 | if (res.length) { 88 | res = res[0]; 89 | } else { 90 | res = null; 91 | } 92 | } 93 | if (res) { 94 | return wrapElement(res); 95 | } else { 96 | return null; 97 | } 98 | }; 99 | 100 | /** 101 | * Searches the page for elements with the given class or tag name (using the '.' prefix 102 | * to specify a class and no prefix for a tag) and returns them as p5.Elements 103 | * in an array. 104 | * The DOM node itself can be accessed with .elt. 105 | * Returns an empty array if none found. 106 | * You can also specify a container to search within. 107 | * 108 | * @method selectAll 109 | * @param {String} name class or tag name of elements to search for 110 | * @param {String} [container] id, p5.Element, or HTML element to search within 111 | * @return {Array} Array of p5.Elements containing nodes found 112 | * @example 113 | *
114 | * function setup() { 115 | * createButton('btn'); 116 | * createButton('2nd btn'); 117 | * createButton('3rd btn'); 118 | * var buttons = selectAll('button'); 119 | * 120 | * for (var i = 0; i < buttons.length; i++){ 121 | * buttons[i].size(100,100); 122 | * } 123 | * } 124 | *
125 | *
126 | * // these are all valid calls to selectAll() 127 | * var a = selectAll('.moo'); 128 | * var b = selectAll('div'); 129 | * var c = selectAll('button', '#myContainer'); 130 | * var d = select('#container'); 131 | * var e = selectAll('p', d); 132 | * var f = document.getElementById('beep'); 133 | * var g = select('.blah', f); 134 | *
135 | * 136 | */ 137 | p5.prototype.selectAll = function (e, p) { 138 | var arr = []; 139 | var res; 140 | var container = getContainer(p); 141 | if (e[0] === '.'){ 142 | e = e.slice(1); 143 | res = container.getElementsByClassName(e); 144 | } else { 145 | res = container.getElementsByTagName(e); 146 | } 147 | if (res) { 148 | for (var j = 0; j < res.length; j++) { 149 | var obj = wrapElement(res[j]); 150 | arr.push(obj); 151 | } 152 | } 153 | return arr; 154 | }; 155 | 156 | /** 157 | * Helper function for select and selectAll 158 | */ 159 | function getContainer(p) { 160 | var container = document; 161 | if (typeof p === 'string' && p[0] === '#'){ 162 | p = p.slice(1); 163 | container = document.getElementById(p) || document; 164 | } else if (p instanceof p5.Element){ 165 | container = p.elt; 166 | } else if (p instanceof HTMLElement){ 167 | container = p; 168 | } 169 | return container; 170 | } 171 | 172 | /** 173 | * Helper function for getElement and getElements. 174 | */ 175 | function wrapElement(elt) { 176 | if(elt.tagName === "INPUT" && elt.type === "checkbox") { 177 | var converted = new p5.Element(elt); 178 | converted.checked = function(){ 179 | if (arguments.length === 0){ 180 | return this.elt.checked; 181 | } else if(arguments[0]) { 182 | this.elt.checked = true; 183 | } else { 184 | this.elt.checked = false; 185 | } 186 | return this; 187 | }; 188 | return converted; 189 | } else if (elt.tagName === "VIDEO" || elt.tagName === "AUDIO") { 190 | return new p5.MediaElement(elt); 191 | } else { 192 | return new p5.Element(elt); 193 | } 194 | } 195 | 196 | /** 197 | * Removes all elements created by p5, except any canvas / graphics 198 | * elements created by createCanvas or createGraphics. 199 | * Event handlers are removed, and element is removed from the DOM. 200 | * @method removeElements 201 | * @example 202 | *
203 | * function setup() { 204 | * createCanvas(100, 100); 205 | * createDiv('this is some text'); 206 | * createP('this is a paragraph'); 207 | * } 208 | * function mousePressed() { 209 | * removeElements(); // this will remove the div and p, not canvas 210 | * } 211 | *
212 | * 213 | */ 214 | p5.prototype.removeElements = function (e) { 215 | for (var i=0; i 243 | * var myDiv; 244 | * function setup() { 245 | * myDiv = createDiv('this is some text'); 246 | * } 247 | * 248 | */ 249 | 250 | /** 251 | * Creates a <p></p> element in the DOM with given inner HTML. Used 252 | * for paragraph length text. 253 | * Appends to the container node if one is specified, otherwise 254 | * appends to body. 255 | * 256 | * @method createP 257 | * @param {String} html inner HTML for element created 258 | * @return {Object|p5.Element} pointer to p5.Element holding created node 259 | * @example 260 | *
261 | * var myP; 262 | * function setup() { 263 | * myP = createP('this is some text'); 264 | * } 265 | *
266 | */ 267 | 268 | /** 269 | * Creates a <span></span> element in the DOM with given inner HTML. 270 | * Appends to the container node if one is specified, otherwise 271 | * appends to body. 272 | * 273 | * @method createSpan 274 | * @param {String} html inner HTML for element created 275 | * @return {Object|p5.Element} pointer to p5.Element holding created node 276 | * @example 277 | *
278 | * var mySpan; 279 | * function setup() { 280 | * mySpan = createSpan('this is some text'); 281 | * } 282 | *
283 | */ 284 | var tags = ['div', 'p', 'span']; 285 | tags.forEach(function(tag) { 286 | var method = 'create' + tag.charAt(0).toUpperCase() + tag.slice(1); 287 | p5.prototype[method] = function(html) { 288 | var elt = document.createElement(tag); 289 | elt.innerHTML = typeof html === undefined ? "" : html; 290 | return addElement(elt, this); 291 | } 292 | }); 293 | 294 | /** 295 | * Creates an <img> element in the DOM with given src and 296 | * alternate text. 297 | * Appends to the container node if one is specified, otherwise 298 | * appends to body. 299 | * 300 | * @method createImg 301 | * @param {String} src src path or url for image 302 | * @param {String} [alt] alternate text to be used if image does not load 303 | * @param {Function} [successCallback] callback to be called once image data is loaded 304 | * @return {Object|p5.Element} pointer to p5.Element holding created node 305 | * @example 306 | *
307 | * var img; 308 | * function setup() { 309 | * img = createImg('http://p5js.org/img/asterisk-01.png'); 310 | * } 311 | *
312 | */ 313 | p5.prototype.createImg = function() { 314 | var elt = document.createElement('img'); 315 | var args = arguments; 316 | var self; 317 | var setAttrs = function(){ 318 | self.width = elt.offsetWidth || elt.width; 319 | self.height = elt.offsetHeight || elt.height; 320 | if (args.length > 1 && typeof args[1] === 'function'){ 321 | self.fn = args[1]; 322 | self.fn(); 323 | }else if (args.length > 1 && typeof args[2] === 'function'){ 324 | self.fn = args[2]; 325 | self.fn(); 326 | } 327 | }; 328 | elt.src = args[0]; 329 | if (args.length > 1 && typeof args[1] === 'string'){ 330 | elt.alt = args[1]; 331 | } 332 | elt.onload = function(){ 333 | setAttrs(); 334 | } 335 | self = addElement(elt, this); 336 | return self; 337 | }; 338 | 339 | /** 340 | * Creates an <a></a> element in the DOM for including a hyperlink. 341 | * Appends to the container node if one is specified, otherwise 342 | * appends to body. 343 | * 344 | * @method createA 345 | * @param {String} href url of page to link to 346 | * @param {String} html inner html of link element to display 347 | * @param {String} [target] target where new link should open, 348 | * could be _blank, _self, _parent, _top. 349 | * @return {Object|p5.Element} pointer to p5.Element holding created node 350 | * @example 351 | *
352 | * var myLink; 353 | * function setup() { 354 | * myLink = createA('http://p5js.org/', 'this is a link'); 355 | * } 356 | *
357 | */ 358 | p5.prototype.createA = function(href, html, target) { 359 | var elt = document.createElement('a'); 360 | elt.href = href; 361 | elt.innerHTML = html; 362 | if (target) elt.target = target; 363 | return addElement(elt, this); 364 | }; 365 | 366 | /** INPUT **/ 367 | 368 | 369 | /** 370 | * Creates a slider <input></input> element in the DOM. 371 | * Use .size() to set the display length of the slider. 372 | * Appends to the container node if one is specified, otherwise 373 | * appends to body. 374 | * 375 | * @method createSlider 376 | * @param {Number} min minimum value of the slider 377 | * @param {Number} max maximum value of the slider 378 | * @param {Number} [value] default value of the slider 379 | * @param {Number} [step] step size for each tick of the slider (if step is set to 0, the slider will move continuously from the minimum to the maximum value) 380 | * @return {Object|p5.Element} pointer to p5.Element holding created node 381 | * @example 382 | *
383 | * var slider; 384 | * function setup() { 385 | * slider = createSlider(0, 255, 100); 386 | * slider.position(10, 10); 387 | * slider.style('width', '80px'); 388 | * } 389 | * 390 | * function draw() { 391 | * var val = slider.value(); 392 | * background(val); 393 | * } 394 | *
395 | * 396 | *
397 | * var slider; 398 | * function setup() { 399 | * colorMode(HSB); 400 | * slider = createSlider(0, 360, 60, 40); 401 | * slider.position(10, 10); 402 | * slider.style('width', '80px'); 403 | * } 404 | * 405 | * function draw() { 406 | * var val = slider.value(); 407 | * background(val, 100, 100, 1); 408 | * } 409 | *
410 | */ 411 | p5.prototype.createSlider = function(min, max, value, step) { 412 | var elt = document.createElement('input'); 413 | elt.type = 'range'; 414 | elt.min = min; 415 | elt.max = max; 416 | if (step === 0) { 417 | elt.step = .000000000000000001; // smallest valid step 418 | } else if (step) { 419 | elt.step = step; 420 | } 421 | if (typeof(value) === "number") elt.value = value; 422 | return addElement(elt, this); 423 | }; 424 | 425 | /** 426 | * Creates a <button></button> element in the DOM. 427 | * Use .size() to set the display size of the button. 428 | * Use .mousePressed() to specify behavior on press. 429 | * Appends to the container node if one is specified, otherwise 430 | * appends to body. 431 | * 432 | * @method createButton 433 | * @param {String} label label displayed on the button 434 | * @param {String} [value] value of the button 435 | * @return {Object|p5.Element} pointer to p5.Element holding created node 436 | * @example 437 | *
438 | * var button; 439 | * function setup() { 440 | * createCanvas(100, 100); 441 | * background(0); 442 | * button = createButton('click me'); 443 | * button.position(19, 19); 444 | * button.mousePressed(changeBG); 445 | * } 446 | * 447 | * function changeBG() { 448 | * var val = random(255); 449 | * background(val); 450 | * } 451 | *
452 | */ 453 | p5.prototype.createButton = function(label, value) { 454 | var elt = document.createElement('button'); 455 | elt.innerHTML = label; 456 | elt.value = value; 457 | if (value) elt.value = value; 458 | return addElement(elt, this); 459 | }; 460 | 461 | /** 462 | * Creates a checkbox <input></input> element in the DOM. 463 | * Calling .checked() on a checkbox returns if it is checked or not 464 | * 465 | * @method createCheckbox 466 | * @param {String} [label] label displayed after checkbox 467 | * @param {boolean} [value] value of the checkbox; checked is true, unchecked is false.Unchecked if no value given 468 | * @return {Object|p5.Element} pointer to p5.Element holding created node 469 | * @example 470 | *
471 | * var checkbox; 472 | * 473 | * function setup() { 474 | * checkbox = createCheckbox('label', false); 475 | * checkbox.changed(myCheckedEvent); 476 | * } 477 | * 478 | * function myCheckedEvent() { 479 | * if (this.checked()) { 480 | * console.log("Checking!"); 481 | * } else { 482 | * console.log("Unchecking!"); 483 | * } 484 | * } 485 | *
486 | */ 487 | p5.prototype.createCheckbox = function() { 488 | var elt = document.createElement('div'); 489 | var checkbox = document.createElement('input'); 490 | checkbox.type = 'checkbox'; 491 | elt.appendChild(checkbox); 492 | //checkbox must be wrapped in p5.Element before label so that label appears after 493 | var self = addElement(elt, this); 494 | self.checked = function(){ 495 | var cb = self.elt.getElementsByTagName('input')[0]; 496 | if (cb) { 497 | if (arguments.length === 0){ 498 | return cb.checked; 499 | }else if(arguments[0]){ 500 | cb.checked = true; 501 | }else{ 502 | cb.checked = false; 503 | } 504 | } 505 | return self; 506 | }; 507 | this.value = function(val){ 508 | self.value = val; 509 | return this; 510 | }; 511 | if (arguments[0]){ 512 | var ran = Math.random().toString(36).slice(2); 513 | var label = document.createElement('label'); 514 | checkbox.setAttribute('id', ran); 515 | label.htmlFor = ran; 516 | self.value(arguments[0]); 517 | label.appendChild(document.createTextNode(arguments[0])); 518 | elt.appendChild(label); 519 | } 520 | if (arguments[1]){ 521 | checkbox.checked = true; 522 | } 523 | return self; 524 | }; 525 | 526 | /** 527 | * Creates a dropdown menu <select></select> element in the DOM. 528 | * @method createSelect 529 | * @param {boolean} [multiple] true if dropdown should support multiple selections 530 | * @return {Object|p5.Element} pointer to p5.Element holding created node 531 | * @example 532 | *
533 | * var sel; 534 | * 535 | * function setup() { 536 | * textAlign(CENTER); 537 | * background(200); 538 | * sel = createSelect(); 539 | * sel.position(10, 10); 540 | * sel.option('pear'); 541 | * sel.option('kiwi'); 542 | * sel.option('grape'); 543 | * sel.changed(mySelectEvent); 544 | * } 545 | * 546 | * function mySelectEvent() { 547 | * var item = sel.value(); 548 | * background(200); 549 | * text("it's a "+item+"!", 50, 50); 550 | * } 551 | *
552 | */ 553 | p5.prototype.createSelect = function(mult) { 554 | var elt = document.createElement('select'); 555 | if (mult){ 556 | elt.setAttribute('multiple', 'true'); 557 | } 558 | var self = addElement(elt, this); 559 | self.option = function(name, value){ 560 | var opt = document.createElement('option'); 561 | opt.innerHTML = name; 562 | if (arguments.length > 1) 563 | opt.value = value; 564 | else 565 | opt.value = name; 566 | elt.appendChild(opt); 567 | }; 568 | self.selected = function(value){ 569 | var arr = []; 570 | if (arguments.length > 0){ 571 | for (var i = 0; i < this.elt.length; i++){ 572 | if (value.toString() === this.elt[i].value){ 573 | this.elt.selectedIndex = i; 574 | } 575 | } 576 | return this; 577 | }else{ 578 | if (mult){ 579 | for (var i = 0; i < this.elt.selectedOptions.length; i++){ 580 | arr.push(this.elt.selectedOptions[i].value); 581 | } 582 | return arr; 583 | }else{ 584 | return this.elt.value; 585 | } 586 | } 587 | }; 588 | return self; 589 | }; 590 | 591 | /** 592 | * Creates a radio button <input></input> element in the DOM. 593 | * The .option() method can be used to set options for the radio after it is 594 | * created. The .value() method will return the currently selected option. 595 | * 596 | * @method createRadio 597 | * @param {String} [divId] the id and name of the created div and input field respectively 598 | * @return {Object|p5.Element} pointer to p5.Element holding created node 599 | * @example 600 | *
601 | * var radio; 602 | * 603 | * function setup() { 604 | * radio = createRadio(); 605 | * radio.option("black"); 606 | * radio.option("white"); 607 | * radio.option("gray"); 608 | * radio.style('width', '60px'); 609 | * textAlign(CENTER); 610 | * fill(255, 0, 0); 611 | * } 612 | * 613 | * function draw() { 614 | * var val = radio.value(); 615 | * background(val); 616 | * text(val, width/2, height/2); 617 | * } 618 | *
619 | *
620 | * var radio; 621 | * 622 | * function setup() { 623 | * radio = createRadio(); 624 | * radio.option('apple', 1); 625 | * radio.option('bread', 2); 626 | * radio.option('juice', 3); 627 | * radio.style('width', '60px'); 628 | * textAlign(CENTER); 629 | * } 630 | * 631 | * function draw() { 632 | * background(200); 633 | * var val = radio.value(); 634 | * if (val) { 635 | * text('item cost is $'+val, width/2, height/2); 636 | * } 637 | * } 638 | *
639 | */ 640 | p5.prototype.createRadio = function() { 641 | var radios = document.querySelectorAll("input[type=radio]"); 642 | var count = 0; 643 | if(radios.length > 1){ 644 | var length = radios.length; 645 | var prev=radios[0].name; 646 | var current = radios[1].name; 647 | count = 1; 648 | for(var i = 1; i < length; i++) { 649 | current = radios[i].name; 650 | if(prev != current){ 651 | count++; 652 | } 653 | prev = current; 654 | } 655 | } 656 | else if (radios.length == 1){ 657 | count = 1; 658 | } 659 | var elt = document.createElement('div'); 660 | var self = addElement(elt, this); 661 | var times = -1; 662 | self.option = function(name, value){ 663 | var opt = document.createElement('input'); 664 | opt.type = 'radio'; 665 | opt.innerHTML = name; 666 | if (arguments.length > 1) 667 | opt.value = value; 668 | else 669 | opt.value = name; 670 | opt.setAttribute('name',"defaultradio"+count); 671 | elt.appendChild(opt); 672 | if (name){ 673 | times++; 674 | var ran = Math.random().toString(36).slice(2); 675 | var label = document.createElement('label'); 676 | opt.setAttribute('id', "defaultradio"+count+"-"+times); 677 | label.htmlFor = "defaultradio"+count+"-"+times; 678 | label.appendChild(document.createTextNode(name)); 679 | elt.appendChild(label); 680 | } 681 | return opt; 682 | }; 683 | self.selected = function(){ 684 | var length = this.elt.childNodes.length; 685 | if(arguments.length == 1) { 686 | for (var i = 0; i < length; i+=2){ 687 | if(this.elt.childNodes[i].value == arguments[0]) 688 | this.elt.childNodes[i].checked = true; 689 | } 690 | return this; 691 | } else { 692 | for (var i = 0; i < length; i+=2){ 693 | if(this.elt.childNodes[i].checked == true) 694 | return this.elt.childNodes[i].value; 695 | } 696 | } 697 | }; 698 | self.value = function(){ 699 | var length = this.elt.childNodes.length; 700 | if(arguments.length == 1) { 701 | for (var i = 0; i < length; i+=2){ 702 | if(this.elt.childNodes[i].value == arguments[0]) 703 | this.elt.childNodes[i].checked = true; 704 | } 705 | return this; 706 | } else { 707 | for (var i = 0; i < length; i+=2){ 708 | if(this.elt.childNodes[i].checked == true) 709 | return this.elt.childNodes[i].value; 710 | } 711 | return ""; 712 | } 713 | }; 714 | return self 715 | }; 716 | 717 | /** 718 | * Creates an <input></input> element in the DOM for text input. 719 | * Use .size() to set the display length of the box. 720 | * Appends to the container node if one is specified, otherwise 721 | * appends to body. 722 | * 723 | * @method createInput 724 | * @param {Number} [value] default value of the input box 725 | * @param {String} [type] type of text, ie text, password etc. Defaults to text 726 | * @return {Object|p5.Element} pointer to p5.Element holding created node 727 | * @example 728 | *
729 | * function setup(){ 730 | * var inp = createInput(''); 731 | * inp.input(myInputEvent); 732 | * } 733 | * 734 | * function myInputEvent(){ 735 | * console.log('you are typing: ', this.value()); 736 | * } 737 | * 738 | *
739 | */ 740 | p5.prototype.createInput = function(value, type) { 741 | var elt = document.createElement('input'); 742 | elt.type = type ? type : 'text'; 743 | if (value) elt.value = value; 744 | return addElement(elt, this); 745 | }; 746 | 747 | /** 748 | * Creates an <input></input> element in the DOM of type 'file'. 749 | * This allows users to select local files for use in a sketch. 750 | * 751 | * @method createFileInput 752 | * @param {Function} [callback] callback function for when a file loaded 753 | * @param {String} [multiple] optional to allow multiple files selected 754 | * @return {Object|p5.Element} pointer to p5.Element holding created DOM element 755 | * @example 756 | * var input; 757 | * var img; 758 | * 759 | * function setup() { 760 | * input = createFileInput(handleFile); 761 | * input.position(0, 0); 762 | * } 763 | * 764 | * function draw() { 765 | * if (img) { 766 | * image(img, 0, 0, width, height); 767 | * } 768 | * } 769 | * 770 | * function handleFile(file) { 771 | * print(file); 772 | * if (file.type === 'image') { 773 | * img = createImg(file.data); 774 | * img.hide(); 775 | * } 776 | * } 777 | */ 778 | p5.prototype.createFileInput = function(callback, multiple) { 779 | 780 | // Is the file stuff supported? 781 | if (window.File && window.FileReader && window.FileList && window.Blob) { 782 | // Yup, we're ok and make an input file selector 783 | var elt = document.createElement('input'); 784 | elt.type = 'file'; 785 | 786 | // If we get a second argument that evaluates to true 787 | // then we are looking for multiple files 788 | if (multiple) { 789 | // Anything gets the job done 790 | elt.multiple = 'multiple'; 791 | } 792 | 793 | // Function to handle when a file is selected 794 | // We're simplifying life and assuming that we always 795 | // want to load every selected file 796 | function handleFileSelect(evt) { 797 | // These are the files 798 | var files = evt.target.files; 799 | // Load each one and trigger a callback 800 | for (var i = 0; i < files.length; i++) { 801 | var f = files[i]; 802 | var reader = new FileReader(); 803 | function makeLoader(theFile) { 804 | // Making a p5.File object 805 | var p5file = new p5.File(theFile); 806 | return function(e) { 807 | p5file.data = e.target.result; 808 | callback(p5file); 809 | }; 810 | }; 811 | reader.onload = makeLoader(f); 812 | 813 | // Text or data? 814 | // This should likely be improved 815 | if (f.type.indexOf('text') > -1) { 816 | reader.readAsText(f); 817 | } else { 818 | reader.readAsDataURL(f); 819 | } 820 | } 821 | } 822 | 823 | // Now let's handle when a file was selected 824 | elt.addEventListener('change', handleFileSelect, false); 825 | return addElement(elt, this); 826 | } else { 827 | console.log('The File APIs are not fully supported in this browser. Cannot create element.'); 828 | } 829 | }; 830 | 831 | 832 | /** VIDEO STUFF **/ 833 | 834 | function createMedia(pInst, type, src, callback) { 835 | var elt = document.createElement(type); 836 | 837 | // allow src to be empty 838 | var src = src || ''; 839 | if (typeof src === 'string') { 840 | src = [src]; 841 | } 842 | for (var i=0; i