├── .gitignore ├── Images ├── capaFundoClaro.png ├── capaFundoEscuro.png ├── logoFundoClaro.png ├── logoFundoEscuro.png └── logoEscuroSemFundo.png ├── public ├── Classes │ ├── Ponto.js │ ├── Historico.js │ ├── Infos.js │ ├── Alimento.js │ ├── Circulo.js │ ├── DNA.js │ ├── Retangulo.js │ ├── Vetor.js │ ├── Carnivoro.js │ ├── Herbivoro.js │ ├── QuadTree.js │ └── Organismo.js ├── style.css ├── chart.js └── index.js ├── README.md └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | 3 | -------------------------------------------------------------------------------- /Images/capaFundoClaro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/e-llo/evolv-e/HEAD/Images/capaFundoClaro.png -------------------------------------------------------------------------------- /Images/capaFundoEscuro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/e-llo/evolv-e/HEAD/Images/capaFundoEscuro.png -------------------------------------------------------------------------------- /Images/logoFundoClaro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/e-llo/evolv-e/HEAD/Images/logoFundoClaro.png -------------------------------------------------------------------------------- /Images/logoFundoEscuro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/e-llo/evolv-e/HEAD/Images/logoFundoEscuro.png -------------------------------------------------------------------------------- /Images/logoEscuroSemFundo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/e-llo/evolv-e/HEAD/Images/logoEscuroSemFundo.png -------------------------------------------------------------------------------- /public/Classes/Ponto.js: -------------------------------------------------------------------------------- 1 | class Ponto { 2 | constructor(x, y, userData){ 3 | this.posicao = new Vetor(x, y); 4 | this.userData = userData; 5 | } 6 | } -------------------------------------------------------------------------------- /public/Classes/Historico.js: -------------------------------------------------------------------------------- 1 | class Historico { 2 | constructor() { 3 | this.herbivoros = new Infos(); 4 | this.carnivoros = new Infos(); 5 | this.segundos = []; 6 | this.taxa_alimentos = []; // Alimentos por segundo 7 | } 8 | 9 | clear() { 10 | this.herbivoros.clear(); 11 | this.carnivoros.clear(); 12 | this.segundos.length = 0; 13 | } 14 | } -------------------------------------------------------------------------------- /public/Classes/Infos.js: -------------------------------------------------------------------------------- 1 | class Infos { 2 | constructor(){ 3 | this.populacao = []; 4 | this.velocidade = []; 5 | this.agilidade = []; 6 | this.raio = []; 7 | this.deteccao = []; 8 | this.energia = []; 9 | this.gasto = []; 10 | this.tamanho_medio_ninhada = []; 11 | } 12 | 13 | clear() { 14 | this.populacao.length = 0 15 | this.velocidade.length = 0 16 | this.agilidade.length = 0 17 | this.raio.length = 0 18 | this.deteccao.length = 0 19 | this.energia.length = 0 20 | this.gasto.length = 0 21 | this.tamanho_medio_ninhada.length = 0; 22 | } 23 | } -------------------------------------------------------------------------------- /public/Classes/Alimento.js: -------------------------------------------------------------------------------- 1 | class Alimento{ 2 | static alimentos = []; 3 | static id = 0; 4 | 5 | constructor(x, y, raio){ 6 | this.posicao = new Vetor(x, y); 7 | this.raio = raio; 8 | // a energia do pedaço de alimento é proporcinal à sua área 9 | this.energia_alimento = Math.floor(Math.PI * Math.pow(this.raio, 2)) * 15; 10 | 11 | Alimento.alimentos.push(this); 12 | 13 | // ID 14 | this.id = Alimento.id++; 15 | } 16 | 17 | display(){ 18 | c.beginPath(); 19 | c.arc(this.posicao.x, this.posicao.y, this.raio, 0, Math.PI * 2); 20 | c.fillStyle = "rgb(115, 158, 115)"; 21 | c.fill(); 22 | } 23 | 24 | 25 | checaId(id){ 26 | return (id == this.id); 27 | } 28 | } -------------------------------------------------------------------------------- /public/Classes/Circulo.js: -------------------------------------------------------------------------------- 1 | class Circulo{ 2 | constructor(x,y,r){ 3 | this.x = x; 4 | this.y = y; 5 | this.r = r; 6 | } 7 | 8 | // Checa se o ponto está contido dentro de seus limites (fronteiras) 9 | contemPonto(ponto){ 10 | let xPonto = ponto.posicao.x; 11 | let yPonto = ponto.posicao.y; 12 | 13 | return( 14 | Math.sqrt(Math.pow(xPonto - this.x, 2) + Math.pow(yPonto - this.y, 2)) <= this.r // Se a distância do ponto até o círculo for menor ou igual ao raio 15 | ); 16 | } 17 | 18 | // Método para saber se os retângulos se interseptam 19 | intersepta(alcance){ 20 | return !( // Se essa expressão for verdade, eles NÃO se interceptam 21 | alcance.x - alcance.w > this.x + this.w || 22 | alcance.x + alcance.w < this.x - this.w || 23 | alcance.y - alcance.h > this.y + this.h || 24 | alcance.y + alcance.h < this.y - this.h 25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /public/Classes/DNA.js: -------------------------------------------------------------------------------- 1 | class DNA{ 2 | constructor(raio_inicial, vel_max, forca_max, cor, raio_deteccao_inicial, intervalo_ninhada, sexo){ 3 | this.raio_inicial = raio_inicial; 4 | this.vel_max = vel_max; 5 | this.forca_max = forca_max; 6 | this.cor = cor; 7 | this.raio_deteccao_inicial = raio_deteccao_inicial; 8 | this.intervalo_ninhada = intervalo_ninhada; 9 | this.sexo = sexo; // string que pode ser XX (fêmea) ou XY (macho) 10 | } 11 | 12 | mutar(){ 13 | var dna_mutado; 14 | 15 | // raio inicial 16 | var raio_inicial_filho = newMutacao(this.raio_inicial); 17 | if(raio_inicial_filho < 0){ 18 | raio_inicial_filho = 0; 19 | } 20 | // velocidade máxima 21 | var vel_max_filho = newMutacao(this.vel_max); 22 | if(vel_max_filho < 0){ 23 | vel_max_filho = 0; 24 | } 25 | 26 | // força máxima 27 | var forca_max_filho = newMutacao(this.forca_max); 28 | 29 | // cor 30 | var cor_filho = corMutacao(this.cor); 31 | 32 | // raio de detecção inicial 33 | var raio_deteccao_inicial_filho = newMutacao(this.raio_deteccao_inicial); 34 | if(raio_deteccao_inicial_filho < raio_inicial_filho){ 35 | raio_deteccao_inicial_filho = raio_inicial_filho; 36 | } 37 | 38 | // tamanho da ninhada 39 | var intervalo_ninhada_filho = mutacaoNinhada(this.intervalo_ninhada[0], this.intervalo_ninhada[1]); 40 | 41 | dna_mutado = new DNA( 42 | raio_inicial_filho, 43 | vel_max_filho, 44 | forca_max_filho, 45 | cor_filho, 46 | raio_deteccao_inicial_filho, 47 | intervalo_ninhada_filho 48 | ) 49 | 50 | return dna_mutado; 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://github.com/evolv-e/evolv-e/blob/main/Images/capaFundoClaro.png) 2 | 3 | ## Genetic Algorithm in Javascript 4 | 5 | * Web application that aims at **simulating evolution** by natural selection using genetic algorithms in Javascript. 6 | 7 | ## 📖How does it work? 8 | It is a 2D virtual environment in which organisms with randomly generated genes evolve over the generations. The user has the ability to alter some of the properties that rule the environment in order to analyse how each gene (attribute) is affected. Each object - organism - may or may not conceive new descendents, which will carry their father's attributes values with themselves, but with a slight chance for each of the genes of undergoing a mutation. 9 | 10 | ## 🧰Tools 11 | The interface used for the simulation is primarily HTML Canvas. However, alongside it there's a dashboard containing interactive graphics which showcase the frequency of each gene in the population over time. 12 | 13 | ## ⁉️Why? 14 | Our goal is to demonstrate how natural selection enables complex populations and behaviors to spontaneously emerge even in rigid environments ruled by simplistic laws. 15 | 16 | --- 17 | ## 👩‍💻 Try it on 18 | * [Click here to see how it works](https://e-llo.github.io/evolv-e/) 19 | * You can also run this application locally 20 | 21 | ## How can I run this locally? 22 | ### ☁️ Clone this repository 23 | ```bash 24 | https://github.com/e-llo/evolv-e.git 25 | ``` 26 | 46 | Then open the index.html file on your browser 47 | 48 | --- 49 | 50 | ## 👩‍💻👨‍💻👨‍💻 Contributors 51 | 52 | 53 | 61 | 69 | 77 | 78 |
54 | 55 | Julia Rolemberg profile picture
56 | 57 | Julia Rolemberg 58 | 59 |
60 |
62 | 63 | Pedro Heck profile picture
64 | 65 | Pedro Heck 66 | 67 |
68 |
70 | 71 | Raphael Menezes profile picture
72 | 73 | Raphael Menezes de Jesus 74 | 75 |
76 |
79 | 80 | -------------------------------------------------------------------------------- /public/Classes/Retangulo.js: -------------------------------------------------------------------------------- 1 | class Retangulo{ 2 | constructor(x,y,w,h){ // o w e o h são a distância do CENTRO até a borda do retângulo! 3 | this.x = x; 4 | this.y = y; 5 | this.w = w; 6 | this.h = h; 7 | } 8 | 9 | // Checa se o ponto está contido dentro de seus limites (fronteiras) 10 | contemPonto(ponto){ 11 | return( 12 | ponto.posicao.x >= this.x - this.w && 13 | ponto.posicao.x <= this.x + this.w && 14 | ponto.posicao.y >= this.y - this.h && 15 | ponto.posicao.y <= this.y + this.h 16 | ); 17 | } 18 | 19 | 20 | // Método para saber se os retângulos se interseptam 21 | interseptaR(alcance){ 22 | return !( // Se essa expressão for verdade, eles NÃO se interceptam 23 | alcance.x - alcance.w > this.x + this.w || 24 | alcance.x + alcance.w < this.x - this.w || 25 | alcance.y - alcance.h > this.y + this.h || 26 | alcance.y + alcance.h < this.y - this.h 27 | ); 28 | } 29 | 30 | // Método para saber se o retângulo intersepta um círculo 31 | interseptaC(circulo){ 32 | // temporary variables to set edges for testing 33 | let testX = circulo.x; 34 | let testY = circulo.y; 35 | 36 | // which edge is closest? 37 | if (circulo.x < this.x - this.w){ 38 | testX = this.x - this.w; 39 | } else if (circulo.x > this.x + this.w){ 40 | testX = this.x + this.w; 41 | } 42 | 43 | if (circulo.y < this.y - this.h){ 44 | testY = this.y - this.h; 45 | } else if (circulo.y > this.y + this.h){ 46 | testY = this.y + this.h; 47 | } 48 | 49 | // get distance from closest edges 50 | let distX = circulo.x - testX; 51 | let distY = circulo.y - testY; 52 | let distance = Math.sqrt((distX*distX) + (distY*distY)); 53 | 54 | // if the distance is less than the radius, collision! 55 | if (distance <= circulo.r) { 56 | return true; 57 | } 58 | return false; 59 | // return !( // Se essa expressão for verdade, eles NÃO se interceptam 60 | // ( // O centro do círculo se encontra fora do retângulo 61 | // circulo.x > this.x + this.w || 62 | // circulo.x < this.x - this.w || 63 | // circulo.y > this.y + this.h || 64 | // circulo.y < this.y - this.h 65 | // ) && ( 66 | // // Nenhum dos vértices do retângulo se encontra dentro do círculo 67 | // // Vértice noroeste 68 | // Math.sqrt(Math.pow((this.x - this.w) - (circulo.x), 2) + Math.pow((this.y - this.h) - (circulo.y), 2)) > circulo.r && 69 | // // Vértice nordeste 70 | // Math.sqrt(Math.pow((this.x + this.w) - (circulo.x), 2) + Math.pow((this.y - this.h) - (circulo.y), 2)) > circulo.r && 71 | // // Vértice sudeste 72 | // Math.sqrt(Math.pow((this.x + this.w) - (circulo.x), 2) + Math.pow((this.y + this.h) - (circulo.y), 2)) > circulo.r && 73 | // // Vértice sudoeste 74 | // Math.sqrt(Math.pow((this.x - this.w) - (circulo.x), 2) + Math.pow((this.y + this.h) - (circulo.y), 2)) > circulo.r 75 | // ) 76 | // ); 77 | } 78 | } -------------------------------------------------------------------------------- /public/Classes/Vetor.js: -------------------------------------------------------------------------------- 1 | // classe para a construção de vetores 2 | class Vetor{ 3 | constructor(x, y){ 4 | this.x = x; 5 | this.y = y; 6 | } 7 | 8 | // reseta os valores x e y do vetor para os valores especificados 9 | set(x,y) { 10 | this.x = x; 11 | this.y = y; 12 | }; 13 | 14 | // retorna o tamanho do vetor ao quadrado 15 | magSq() { 16 | var x = this.x, y = this.y; 17 | return x * x + y * y; 18 | }; 19 | 20 | // retorna o tamanho do vetor 21 | mag(){ 22 | return Math.sqrt(this.magSq()); 23 | }; 24 | 25 | // soma o vetor atual com um novo especificado e retorna o próprio vetor (atualizado), e não um novo 26 | add(v) { 27 | this.x += v.x; 28 | this.y += v.y; 29 | return this; 30 | }; 31 | 32 | // subtrai um vetor especificado do atual e retorna o próprio vetor (atualizado), e não um novo 33 | sub(v) { 34 | this.x -= v.x; 35 | this.y -= v.y; 36 | return this; 37 | }; 38 | 39 | // subtrai um vetor especificado do atual e retorna um novo 40 | subNew(v) { 41 | var x = this.x - v.x; 42 | var y = this.y - v.y; 43 | return new Vetor(x, y); 44 | }; 45 | 46 | // retorna este vetor após dividí-lo por um valor especificado 47 | // serve para diminuir o tamanho de um vetor. Assim, se n for dois, o vetor terá a metade do tamanho 48 | div(n) { 49 | this.x /= n; 50 | this.y /= n; 51 | return this; 52 | }; 53 | 54 | // retorna este vetor após multiplicá-lo por um valor especificado 55 | // serve para aumentar o tamanho de um vetor. Assim, se n for dois, o vetor terá o dobro do tamanho 56 | mul(n) { 57 | this.x *= n; 58 | this.y *= n; 59 | return this; 60 | }; 61 | 62 | // muda o tamanho de um vetor pra 1 (isso se chama normalizar um vetor) 63 | normalize() { 64 | // divide o próprio vetor pelo vetor retornado em mag(), ou seja, divide ele mesmo pelo seu tamanho, 65 | // resultando em 1 66 | return this.div(this.mag()); 67 | }; 68 | 69 | // muda o tamanho do vetor para um valor especificado 70 | setMag(n) { 71 | // normaliza (muda o tamanho para 1) e então multiplica por n 72 | return this.normalize().mul(n); 73 | }; 74 | 75 | // retorna a distância entre dois pontos (definidos por x e y de um vetor v) 76 | dist(v) { 77 | var d = v.copy().sub(this); 78 | return d.mag(); 79 | }; 80 | 81 | // limita o tamanho do vetor para um valor limite (usamos esse método para limitar a velocidade, por exemplo) 82 | limit(l) { 83 | var mSq = this.magSq(); 84 | if(mSq > l*l) { 85 | this.div(Math.sqrt(mSq)); 86 | this.mul(l); 87 | } 88 | return this; 89 | }; 90 | 91 | // retorna a direção pra qual o vetor está apondando (em radianos) 92 | headingRads() { 93 | var h = Math.atan2(this.y, this.x); 94 | return h; 95 | }; 96 | 97 | // retorna a direção pra qual o vetor está apondando (em graus) 98 | headingDegs() { 99 | var r = Math.atan2(this.y, this.x); 100 | var h = (r * 180.0) / Math.PI; 101 | return h; 102 | }; 103 | 104 | // rotaciona o vetor em 'a' radianos 105 | // podemos usar isso para que o desenho do bichinho rotacione pra estar sempre alinhado a seu movimento 106 | rotateRads(a) { 107 | var newHead = this.headingRads() + a; 108 | var mag = this.mag(); 109 | this.x = Math.cos(newHead) * mag; 110 | this.y = Math.sin(newHead) * mag; 111 | return this; 112 | }; 113 | 114 | // rotaciona o vetor em 'a' graus 115 | rotateDegs(a) { 116 | a = (a * Math.PI)/180.0; 117 | var newHead = this.headingRads() + a; 118 | var mag = this.mag(); 119 | this.x = Math.cos(newHead) * mag; 120 | this.y = Math.sin(newHead) * mag; 121 | return this; 122 | }; 123 | 124 | // retorna o ângulo entre dois vetores (em radianos) --> /\ 125 | angleBetweenDegs(x,y) { 126 | var r = this.angleBetweenRads(x,y); 127 | var d = (r * 180)/Math.PI; 128 | return d; 129 | } 130 | 131 | // checa se dois vetores são idênticos e retorna um booleano 132 | equals(x, y) { 133 | var a, b; 134 | if (x instanceof Vetor) { 135 | a = x.x || 0; 136 | b = x.y || 0; 137 | } else { 138 | a = x || 0; 139 | b = y || 0; 140 | } 141 | 142 | return this.x === a && this.y === b; 143 | }; 144 | 145 | // retorna uma cópia deste vetor 146 | copy(){ 147 | return new Vetor(this.x,this.y); 148 | } 149 | 150 | } -------------------------------------------------------------------------------- /public/Classes/Carnivoro.js: -------------------------------------------------------------------------------- 1 | class Carnivoro extends Organismo{ 2 | static carnivoros = []; 3 | static highlight = false; 4 | 5 | constructor(x, y, dna, pai = null){ 6 | super(x, y, dna, pai); // referenciando o construtor da classe mãe 7 | 8 | // variável para contar quando um carnívoro poderá se reproduzir 9 | this.contagem_pra_reproducao = 0; 10 | 11 | Carnivoro.carnivoros.push(this); 12 | } 13 | 14 | // Método de reprodução (com mutações) 15 | reproduzir(){ 16 | this.vezes_reproduzidas++; 17 | 18 | var dna_filho = this._reproduzir(); 19 | var filho = new Carnivoro( 20 | this.posicao.x, this.posicao.y, dna_filho, this 21 | ); 22 | 23 | this.filhos.push(filho); 24 | 25 | return filho; 26 | } 27 | 28 | reproduzirSexuado(parceiro){ 29 | this.vezes_reproduzidas++; 30 | 31 | var dna_filho = this.combinaDnas(parceiro); 32 | var filho = new Carnivoro( 33 | this.posicao.x, this.posicao.y, dna_filho, this 34 | ) 35 | 36 | this.filhos.push(filho); 37 | 38 | return filho; 39 | } 40 | 41 | morre(){ 42 | if(this.popover_id) deletePopover(this.popover_id, this.id); 43 | Carnivoro.carnivoros = super.remove(Carnivoro.carnivoros, this); 44 | Organismo.organismos = super.remove(Organismo.organismos, this); 45 | } 46 | 47 | buscarHerbivoro(qtree, visaoC){ 48 | this.status = "procurando presas" 49 | this.comendo = false; 50 | // Var recorde: qual a menor distância (a recorde) de um herbivoro até agora 51 | var recorde = Infinity; // Inicialmente, setaremos essa distância como sendo infinita 52 | var i_mais_perto = -1; // Qual o índice na lista de herbivoros do herbivoro mais perto até agora 53 | 54 | // Insere em herbivoros_proximos uma lista de herbivoros que estão na sua QuadTree 55 | let herbivoros_proximos = qtree.procuraHerbivoros(visaoC); // procuraHerbivoros() retorna uma lista de herbivoros 56 | // console.log("herbivoros próximos", herbivoros_proximos); 57 | 58 | // Loop que analisa cada herbivoro na lista de herbivoros 59 | for(var i = herbivoros_proximos.length - 1; i >= 0; i--){ 60 | // Distância d entre este organismo e o atual herbivoro sendo analisado na lista (lista_herbivoros[i]) 61 | // var d = this.posicao.dist(lista_herbivoros[i].posicao); 62 | 63 | var d2 = Math.pow(this.posicao.x - herbivoros_proximos[i].posicao.x, 2) + Math.pow(this.posicao.y - herbivoros_proximos[i].posicao.y, 2); 64 | 65 | if (d2 <= recorde){ // Caso a distância seja menor que a distância recorde, 66 | recorde = d2; // recorde passa a ter o valor de d 67 | i_mais_perto = i; // e o atual alimento passa a ser o i_mais_perto 68 | } 69 | 70 | } 71 | // Momento em que ele vai comer! 72 | if(recorde <= Math.pow(this.raio_deteccao, 2)){ 73 | this.comendo = true; 74 | this.vagueando = false; 75 | this.status = "caçando" 76 | 77 | herbivoros_proximos[i_mais_perto].fugindo = true; 78 | herbivoros_proximos[i_mais_perto].comendo = false; 79 | herbivoros_proximos[i_mais_perto].vagueando = false; 80 | herbivoros_proximos[i_mais_perto].status = "fugindo"; 81 | 82 | if(recorde <= 25){ // como recorde é a distância ao quadrado, elevamos 5 ao quadrado (5^2 = 25) para comparar 83 | 84 | // Loop para achar o herbívoro que contenha o id do herbívoro mais próximo a fim de deletá-lo da lista estática com base em seu id 85 | // Herbivoro.herbivoros.every(h => { 86 | // if(h.checaId(herbivoros_proximos[i_mais_perto].id)){ 87 | // return false; 88 | // } 89 | 90 | // return true; 91 | // }); 92 | 93 | this.comeHerbivoro(herbivoros_proximos[i_mais_perto]); 94 | 95 | /////////////////////////////////////////////////////////////////////////////// 96 | // this.contagem_pra_reproducao++; 97 | 98 | // if(this.contagem_pra_reproducao >= 4){ // se o carnívoro comer herbívoros 99 | // if(Math.random() < this.chance_de_reproducao ){ // chance de se reproduzir 100 | // this.reproduzir(); 101 | // } 102 | // this.contagem_pra_reproducao = 0; // reseta a variável para que possa se reproduzir outras vezes 103 | // } 104 | /////////////////////////////////////////////////////////////////////////////// 105 | 106 | } else if(herbivoros_proximos.length != 0){ 107 | this.persegue(herbivoros_proximos[i_mais_perto]); 108 | } 109 | } 110 | } 111 | 112 | comeHerbivoro(herbivoro){ 113 | this.qdade_comida++; 114 | // Absorção de energia ao comer o herbívoro 115 | // Se a energia que ele adquirá do herbívoro (10% da energia total do herbívoro) 116 | // for menor que o quanto falta para encher a barra de energia, ela será somada integralmente (os 10%) 117 | if(this.energia_max - this.energia >= herbivoro.energia_max * 0.1){ 118 | this.energia += herbivoro.energia_max * 0.1; // O carnívoro, ao comer o herbívoro, ganha 10% da energia deste 119 | } else{ 120 | this.energia = this.energia_max; // Limitanto a energia para não ultrapassar sua energia máxima 121 | } 122 | if(this.energia > this.energia_max){ 123 | this.energia = this.energia_max; 124 | } 125 | herbivoro.morre() // O herbívoro comido morre (é retirado da lista de herbívoros) 126 | this.aumentaTamanho(); 127 | } 128 | 129 | display(){ 130 | c.beginPath(); 131 | c.arc(this.posicao.x, this.posicao.y, this.raio, 0, Math.PI * 2); 132 | 133 | if(Herbivoro.highlight) { 134 | c.fillStyle = "rgba(" + this.cor2.substr(5).replace(")","") + ",0.15)"; 135 | c.strokeStyle = "rgba(" + this.cor.substr(4).replace(")","") + ",0.15)"; 136 | } else { 137 | c.fillStyle = this.cor2; 138 | c.strokeStyle = this.cor; 139 | } 140 | c.lineWidth = 5; 141 | c.stroke(); 142 | c.fill(); 143 | 144 | // desenhando o raio de detecção 145 | // c.beginPath(); 146 | // c.arc(this.posicao.x, this.posicao.y, this.raio_deteccao, 0, Math.PI * 2); 147 | // c.strokeStyle = "grey"; 148 | // c.stroke(); 149 | } 150 | } -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | font-family: 'Montserrat', sans-serif; 4 | } 5 | 6 | body{ 7 | 8 | } 9 | 10 | canvas { 11 | background-color: #333; 12 | max-width: 100%; 13 | } 14 | 15 | #img-splash{ 16 | display: block; 17 | margin-left: auto; 18 | margin-right: auto; 19 | width: 50%; 20 | } 21 | 22 | #chart{ 23 | width: 480px; 24 | margin: 0 auto; 25 | 26 | } 27 | #link{ 28 | position: absolute; 29 | top: 0; 30 | left: 0; 31 | } 32 | .cronometro { 33 | position: absolute; 34 | bottom: 0; 35 | left: 0; 36 | color: #bbb; 37 | z-index: 4; 38 | } 39 | .framerate{ 40 | position: absolute; 41 | top: 0; 42 | left: 0; 43 | } 44 | 45 | .splash { 46 | position: absolute; 47 | top:0; right:0; 48 | height: 100%; 49 | width: 100%; 50 | z-index: 3000; 51 | color: #fff; 52 | font-size: 70px; 53 | } 54 | 55 | .slidecontainer { 56 | width: 100%; /* Width of the outside container */ 57 | color: #ddd; 58 | } 59 | 60 | .slider { 61 | -webkit-appearance: none; 62 | width: 100%; 63 | height: 5px; 64 | border-radius: 5px; 65 | background: #fff; 66 | outline: none; 67 | opacity: 0.7; 68 | -webkit-transition: .2s; 69 | transition: opacity .2s; 70 | } 71 | 72 | .slider::-webkit-slider-thumb { 73 | -webkit-appearance: none; 74 | appearance: none; 75 | width: 12px; 76 | height: 12px; 77 | border-radius: 50%; 78 | background: green; 79 | cursor: pointer; 80 | } 81 | 82 | .slider::-moz-range-thumb { 83 | width: 12px; 84 | height: 12px; 85 | border-radius: 50%; 86 | background: green; 87 | cursor: pointer; 88 | } 89 | .slider-red { 90 | -webkit-appearance: none; 91 | width: 100%; 92 | height: 5px; 93 | border-radius: 5px; 94 | background: #fff; 95 | outline: none; 96 | opacity: 0.7; 97 | -webkit-transition: .2s; 98 | transition: opacity .2s; 99 | } 100 | 101 | .slider-red::-webkit-slider-thumb { 102 | -webkit-appearance: none; 103 | appearance: none; 104 | width: 12px; 105 | height: 12px; 106 | border-radius: 50%; 107 | background: red; 108 | cursor: pointer; 109 | } 110 | 111 | .slider-red::-moz-range-thumb { 112 | width: 12px; 113 | height: 12px; 114 | border-radius: 50%; 115 | background: red; 116 | cursor: pointer; 117 | } 118 | 119 | 120 | .slider-yellow { 121 | -webkit-appearance: none; 122 | width: 100%; 123 | height: 5px; 124 | border-radius: 5px; 125 | background: #fff; 126 | outline: none; 127 | opacity: 0.7; 128 | -webkit-transition: .2s; 129 | transition: opacity .2s; 130 | } 131 | 132 | .slider-yellow::-webkit-slider-thumb { 133 | -webkit-appearance: none; 134 | appearance: none; 135 | width: 12px; 136 | height: 12px; 137 | border-radius: 50%; 138 | background: rgb(255, 187, 0); 139 | cursor: pointer; 140 | } 141 | 142 | .slider-yellow::-moz-range-thumb { 143 | width: 12px; 144 | height: 12px; 145 | border-radius: 50%; 146 | background: rgb(255, 187, 0); 147 | cursor: pointer; 148 | } 149 | 150 | .slider-blue { 151 | -webkit-appearance: none; 152 | width: 100%; 153 | height: 5px; 154 | border-radius: 5px; 155 | background: #fff; 156 | outline: none; 157 | opacity: 0.7; 158 | -webkit-transition: .2s; 159 | transition: opacity .2s; 160 | } 161 | 162 | .slider-blue::-webkit-slider-thumb { 163 | -webkit-appearance: none; 164 | appearance: none; 165 | width: 12px; 166 | height: 12px; 167 | border-radius: 50%; 168 | background: rgb(31, 228, 211); 169 | cursor: pointer; 170 | } 171 | 172 | .slider-blue::-moz-range-thumb { 173 | width: 12px; 174 | height: 12px; 175 | border-radius: 50%; 176 | background: rgb(31, 228, 211); 177 | cursor: pointer; 178 | } 179 | 180 | #btnAli{ 181 | padding-right: 20%; 182 | } 183 | 184 | .btn-circle.btn-xl { 185 | width: 30px; 186 | height: 30px; 187 | padding: 0px 0px; 188 | border-radius: 15px; 189 | font-size: 16px; 190 | text-align: center; 191 | } 192 | 193 | .btn-gray { 194 | padding: 0; 195 | } 196 | 197 | .btn-gray > i { 198 | color: #555; 199 | } 200 | 201 | .btn-gray:hover > i, .btn-gray.active > i{ 202 | color: #eee; 203 | } 204 | #secaoExcluir{ 205 | font-weight: 700; 206 | margin-bottom: 5px; 207 | 208 | } 209 | 210 | .sidenav { 211 | height: 100%; 212 | position: fixed; 213 | z-index: 1; 214 | top: 0; 215 | right: 0; 216 | background-color: #111; 217 | overflow-x: hidden; 218 | transition: 0.5s; 219 | padding-top: 60px; 220 | } 221 | 222 | .sidenav a { 223 | padding: 8px 8px 8px 32px; 224 | text-decoration: none; 225 | font-size: 25px; 226 | color: #818181; 227 | display: block; 228 | transition: 0.3s; 229 | } 230 | 231 | .sidenav a:hover { 232 | color: #f1f1f1; 233 | } 234 | 235 | .tab-info{ 236 | position: absolute; 237 | /* border: solid 1px #888; */ 238 | border-radius: 5px; 239 | background: rgb(70, 70, 70, 0.4); 240 | backdrop-filter: blur(3px); 241 | color: #fff; 242 | z-index: 2; 243 | } 244 | 245 | .tab-title { 246 | cursor: move; 247 | display: flex; 248 | justify-content: space-between; 249 | align-items: center; 250 | background: #222; 251 | backdrop-filter: blur(3px); 252 | font-size: 1.2em; 253 | font-weight: bold; 254 | border-radius: 5px 5px 0 0; 255 | /* border: solid #bbb 1px; */ 256 | border-bottom: solid #bbb 2px; 257 | border-top: none; 258 | padding: 10px; 259 | } 260 | 261 | .tab-body { 262 | padding: 10px; 263 | max-width: 500px; 264 | } 265 | 266 | .close-btn > i { 267 | color: #fff; 268 | } 269 | 270 | .w-300px{ 271 | width: 300px; 272 | } 273 | 274 | @media screen and (max-height: 450px) { 275 | .sidenav {padding-top: 15px;} 276 | .sidenav a {font-size: 18px;} 277 | } 278 | @media (max-width: 450px) { 279 | #chart{ 280 | width: 100%; 281 | margin: 0 auto; 282 | } 283 | body{ 284 | overflow: auto; 285 | } 286 | 287 | } 288 | 289 | .addObjeto{ 290 | margin: 3px 0; 291 | } 292 | 293 | .popover-info{ 294 | position: absolute; 295 | width: 250px; 296 | /* border: solid 1px #888; */ 297 | border-radius: 5px; 298 | background: rgb(70, 70, 70, 0.4); 299 | backdrop-filter: blur(3px); 300 | color: #fff; 301 | z-index: 9999; 302 | } 303 | 304 | /* SETA */ 305 | .popover-info:before { 306 | content: ""; 307 | position: absolute; 308 | top: 16px; 309 | left: -12px; 310 | border-style: solid; 311 | border-width: 9px 9px 0; 312 | border-color: #bbb transparent; 313 | transform: rotate(90deg); 314 | display: block; 315 | width: 0; 316 | z-index: 0; 317 | } 318 | 319 | .popover-info:after { 320 | content: ""; 321 | position: absolute; 322 | top: 16px; 323 | left: -9px; 324 | border-style: solid; 325 | border-width: 8px 8px 0; 326 | border-color: #222 transparent; 327 | transform: rotate(90deg); 328 | display: block; 329 | width: 0; 330 | z-index: 1; 331 | } 332 | /* ------ SETA */ 333 | 334 | .popover-title { 335 | background-color: #222; 336 | font-size: 1.2em; 337 | font-weight: bold; 338 | border-radius: 5px 5px 0 0; 339 | /* border: solid #bbb 1px; */ 340 | border-bottom: solid #bbb 2px; 341 | border-top: none; 342 | padding: 10px; 343 | } 344 | 345 | .popover-content { 346 | padding: 10px; 347 | } 348 | 349 | .popover-info > .close, 350 | .popover-info > .close:hover { 351 | position: absolute; 352 | top: 0; 353 | right:0; 354 | color: #fff; 355 | font-size: 25px; 356 | } 357 | 358 | .edit-organism { 359 | width: 100%; 360 | } 361 | 362 | .edit-organism input.form-control { 363 | background-color: #333; 364 | border-color: #222; 365 | color: #eee; 366 | } 367 | 368 | .edit-organism input[type=color] { 369 | background-color: #222; 370 | border: none; 371 | } 372 | 373 | .edit-organism input[disabled] { 374 | background-color: #222; 375 | color: #777; 376 | } 377 | #edit-title{ 378 | margin-top: auto; 379 | margin-bottom: auto; 380 | font-weight: 700; 381 | } 382 | #input-cor{ 383 | height:100%; 384 | } 385 | 386 | .sideTitle { 387 | text-align: center; 388 | font-size: 20px; 389 | max-width: 100%; 390 | overflow: hidden; 391 | white-space: nowrap; 392 | } 393 | 394 | .sideTitle:before, .sideTitle:after { 395 | content:" - - - - - - - - - - " 396 | } 397 | 398 | .icone-vegetal{ 399 | display: inline-block; 400 | vertical-align: middle; 401 | width: 5px; 402 | height: 5px; 403 | border-radius: 5px; 404 | background-color: #fff9; 405 | } 406 | 407 | .icone-herbivoro{ 408 | display: inline-block; 409 | vertical-align: middle; 410 | width: 20px; 411 | height: 10px; 412 | border-radius: 50%; 413 | background-color: #fff9; 414 | } 415 | 416 | .icone-carnivoro{ 417 | display: inline-block; 418 | vertical-align: middle; 419 | width: 20px; 420 | height: 20px; 421 | border-radius: 20px; 422 | border: 4px solid #fff; 423 | background-color: #fff9; 424 | } 425 | #linkGit{ 426 | margin-left: 10px; 427 | text-decoration: none; 428 | color: white; 429 | opacity: 0.5 430 | } 431 | #linkGit:hover{ 432 | opacity: 0.9; 433 | 434 | } -------------------------------------------------------------------------------- /public/Classes/Herbivoro.js: -------------------------------------------------------------------------------- 1 | class Herbivoro extends Organismo{ 2 | static herbivoros = []; 3 | static highlight = false; 4 | 5 | constructor(x, y, dna, pai = null){ 6 | super(x, y, dna, pai); 7 | 8 | // variável para contar quando um herbívoro poderá se reproduzir 9 | this.contagem_pra_reproducao = 0; 10 | 11 | Herbivoro.herbivoros.push(this); 12 | } 13 | 14 | // Método de reprodução (com mutações) 15 | reproduzir(){ 16 | this.vezes_reproduzidas++; 17 | 18 | var dna_filho = this._reproduzir(); 19 | var filho = new Herbivoro( 20 | this.posicao.x, this.posicao.y, dna_filho, this 21 | ); 22 | 23 | this.filhos.push(filho); 24 | 25 | return filho; 26 | } 27 | 28 | reproduzirSexuado(parceiro){ 29 | this.vezes_reproduzidas++; 30 | 31 | var dna_filho = this.combinaDnas(parceiro); 32 | var filho = new Herbivoro( 33 | this.posicao.x, this.posicao.y, dna_filho, this 34 | ) 35 | 36 | this.filhos.push(filho); 37 | 38 | return filho; 39 | } 40 | 41 | morre(){ 42 | if(this.popover_id) deletePopover(this.popover_id, this.id); 43 | Herbivoro.herbivoros = super.remove(Herbivoro.herbivoros, this); 44 | Organismo.organismos = super.remove(Organismo.organismos, this) 45 | } 46 | 47 | buscarAlimento(qtree, visaoH){ 48 | this.status = "buscando alimento"; 49 | this.comendo = false; 50 | // Var recorde: qual a menor distância (a recorde) de um alimento até agora 51 | var recorde = Infinity; // Inicialmente, setaremos essa distância como sendo infinita 52 | var i_mais_perto = -1; // Qual o índice do alimento mais perto até agora 53 | 54 | // Insere em alimentos_proximos uma lista de alimentos que estão na sua QuadTree 55 | let alimentos_proximos = qtree.procuraAlimentos(visaoH); // procuraAlimentos() retorna uma lista de alimentos 56 | 57 | // console.log("alimentos proximos: ", alimentos_proximos); 58 | 59 | for(var i = alimentos_proximos.length - 1; i >= 0 ; i--){ 60 | // Distância d entre este organismo e o atual alimento sendo analisado na lista 61 | // var d = this.posicao.dist(alimentos_proximos[i].posicao); 62 | 63 | var d2 = Math.pow(this.posicao.x - alimentos_proximos[i].posicao.x, 2) + Math.pow(this.posicao.y - alimentos_proximos[i].posicao.y, 2); 64 | 65 | 66 | if (d2 <= recorde){ // Caso a distância seja menor que a distância recorde, 67 | recorde = d2; // recorde passa a ter o valor de d 68 | i_mais_perto = i; // e o atual alimento passa a ser o i_mais_perto 69 | } 70 | } 71 | 72 | // Momento em que ele vai comer! 73 | if(recorde <= Math.pow(this.raio_deteccao, 2)){ 74 | this.comendo = true; 75 | this.vagueando = false; 76 | this.status = "pegando alimento"; 77 | 78 | if(recorde <= 25){ // como recorde é a distância ao quadrado, elevamos 5 ao quadrado (5^2 = 25) para comparar 79 | 80 | let indice_lista_estatica = 0; 81 | 82 | // Loop para achar o alimento que contenha o id do alimento mais próximo a fim de deletá-lo da lista estática com base em seu id 83 | Alimento.alimentos.every(a => { 84 | if(a.checaId(alimentos_proximos[i_mais_perto].id)){ 85 | return false; 86 | } 87 | indice_lista_estatica++; 88 | 89 | return true; 90 | }); 91 | 92 | this.comeAlimento(alimentos_proximos[i_mais_perto], indice_lista_estatica); 93 | 94 | /////////////////////////////////////////////////////////////////////////////// 95 | // this.contagem_pra_reproducao++; 96 | 97 | // if(this.contagem_pra_reproducao >= 3){ // se o herbívoro comer alimentos 98 | // if(Math.random() < this.chance_de_reproducao){ // chance de se reproduzir 99 | // this.reproduzir(); 100 | // } 101 | // this.contagem_pra_reproducao = 0; // reseta a variável para que possa se reproduzir outras vezes 102 | // } 103 | ////////////////////////////////////////////////////////////////////////////////////////// 104 | 105 | } else if(alimentos_proximos.length != 0){ 106 | this.persegue(alimentos_proximos[i_mais_perto]); 107 | } 108 | } 109 | } 110 | 111 | comeAlimento(alimento, i){ 112 | this.qdade_comida++; 113 | // Absorção de energia ao comer alimento: 114 | // Se a energia do alimento for menor que o quanto falta para encher a barra de energia, 115 | // o herbívoro adquire ela toda 116 | if(this.energia_max - this.energia >= alimento.energia_alimento * 0.1){ 117 | this.energia += alimento.energia_alimento * 0.1; 118 | } else{ 119 | this.energia = this.energia_max; // Limitanto a energia para não ultrapassar sua energia máxima 120 | } 121 | if(this.energia > this.energia_max){ 122 | this.energia = this.energia_max; 123 | } 124 | Alimento.alimentos.splice(i, 1); // Retira o alimento da lista de alimentos 125 | this.aumentaTamanho(); 126 | } 127 | 128 | // Método para detectar um predador (basicamente idêntico ao buscarAlimento()) 129 | // O método faz o herbívoro achar o carnívoro mais próximo e, se ele estiver dentro de seu raio de detecção, 130 | // ele aciona o método fugir() 131 | detectaPredador(qtree, visaoH){ 132 | this.fugindo = false; 133 | // Var recorde: qual a menor distância (a recorde) de um carnívoro até agora 134 | var recorde = Infinity; // Inicialmente, setaremos essa distância como sendo infinita 135 | var i_mais_perto = -1; // Qual o índice do carnívoro mais perto até agora 136 | 137 | // Insere em carnivoros_proximos uma lista de carnívoros que estão na sua QuadTree 138 | let carnivoros_proximos = qtree.procuraCarnivoros(visaoH); // procuraCarnivoros() retorna uma lista de carnívoros 139 | 140 | // console.log("carnívoros próximos: ", qtree.procuraCarnivoros(visaoH)); 141 | 142 | // Loop que analisa cada carnívoro na lista de carnívoros próximos 143 | for(var i = carnivoros_proximos.length - 1; i >= 0; i--){ 144 | // Distância d entre este organismo e o atual carnívoro sendo analisado na lista (lista_predadores[i]) 145 | // var d = this.posicao.dist(carnivoros_proximos[i].posicao); 146 | 147 | var d2 = Math.pow(this.posicao.x - carnivoros_proximos[i].posicao.x, 2) + Math.pow(this.posicao.y - carnivoros_proximos[i].posicao.y, 2); 148 | 149 | if (d2 <= recorde){ // Caso a distância seja menor que a distância recorde, 150 | recorde = d2; // recorde passa a ter o valor de d 151 | i_mais_perto = i; // e o atual carnívoro passa a ser o i_mais_perto 152 | } 153 | 154 | } 155 | 156 | // Momento em que ele vai fugir! 157 | if(recorde <= Math.pow(this.raio_deteccao, 2)){ 158 | 159 | if(carnivoros_proximos.length != 0){ 160 | // Chamada do método foge(), que muda a velocidade do herbívoro para a direção oposta ao do predador 161 | this.foge(carnivoros_proximos[i_mais_perto]); 162 | } 163 | } 164 | 165 | } 166 | 167 | // Método que atualiza a velocidade (portanto a posição) do herbívoro a fim de fazê-lo andar na direção contrária 168 | // à do carnívoro 169 | foge(alvo){ 170 | // O vetor da velocidade desejada é o vetor de posição do alvo menos o da própria posição 171 | var vel_desejada = alvo.posicao.subNew(this.posicao); // Um vetor apontando da localização dele para o alvo 172 | // Por enquanto, a velocidade desejada está apontando para o carnívoro. Assim, precisamos inverter os valores 173 | // de x e de y do vetor para que ele aponte para o lado oposto. É como num plano cartesiano: se invertermos o x, 174 | // a reta é espelhada verticalmente. Se invertermos só o y, é espelhada horizontalmente. Se invertermos ambos, 175 | // a reta fica diametralmente oposta, ou seja, aponta exatamente para a direção contrária. 176 | vel_desejada.x = -vel_desejada.x; // Invertendo x 177 | vel_desejada.y = -vel_desejada.y // Invertendo y 178 | // Amplia a velocidade desejada para a velocidade máxima do herbívoro 179 | vel_desejada.setMag(this.vel_max); 180 | 181 | // Redirecionamento = velocidade desejada - velocidade. Trata-se da força que será aplicada no herbívoro 182 | // para que sua velocidade mude de direção 183 | var redirecionamento = vel_desejada.subNew(this.vel); 184 | redirecionamento.limit(this.forca_max); // Limita o redirecionamento para a força máxima 185 | 186 | // Soma a força de redirecionamento à aceleração 187 | this.aplicaForca(redirecionamento); 188 | } 189 | 190 | display(){ 191 | // var direcao = this.vel.headingDegs(); 192 | c.beginPath(); 193 | // desenhaOval(c, this.posicao.x, this.posicao.y, this.raio*2, this.raio, 'red'); 194 | c.ellipse(this.posicao.x, this.posicao.y, this.raio * 0.7, this.raio * 1.1, this.vel.headingRads() - Math.PI/2, 0, Math.PI * 2); 195 | // console.log(this.vel.headingDegs()); 196 | if(Carnivoro.highlight) { 197 | c.fillStyle = "rgba(" + this.cor.substr(4).replace(")","") + ",0.15)"; 198 | c.strokeStyle = "rgba(" + this.cor.substr(4).replace(")","") + ",0.15)"; 199 | } else { 200 | c.fillStyle = this.cor; 201 | c.strokeStyle = this.cor; 202 | } 203 | 204 | c.fill(); 205 | // desenhando o raio de detecção 206 | // c.beginPath(); 207 | // c.arc(this.posicao.x, this.posicao.y, this.raio_deteccao, 0, Math.PI * 2); 208 | // c.strokeStyle = "grey"; 209 | // c.stroke(); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /public/Classes/QuadTree.js: -------------------------------------------------------------------------------- 1 | class QuadTree{ 2 | constructor(limite, capacidade){ 3 | this.limite = limite; // Atributo do tipo Retângulo 4 | this.capacidade = capacidade; // A partir de quantos pontos (neste caso, seres vivos) o retângulo se subdivide 5 | this.pontos = []; 6 | this.alimentos = []; 7 | this.herbivoros = []; 8 | this.carnivoros = []; 9 | // this.seresVivos = this.alimentos.concat(this.herbivoros, this.carnivoros); // Array contendo todos os alimentos, herbívoros e carnívoros dentro de sua raiz 10 | this.dividida = false; 11 | } 12 | 13 | // Subdivide a QuadTree em 4 retângulos filhos 14 | subdivide(){ 15 | let x = this.limite.x; 16 | let y = this.limite.y; 17 | let w = this.limite.w; 18 | let h = this.limite.h; 19 | 20 | let ne = new Retangulo(x + w/2, y - h/2, w/2, h/2); 21 | this.nordeste = new QuadTree(ne, this.capacidade); 22 | 23 | let no = new Retangulo(x - w/2, y - h/2, w/2, h/2); 24 | this.noroeste = new QuadTree(no, this.capacidade); 25 | 26 | let se = new Retangulo(x + w/2, y + h/2, w/2, h/2); 27 | this.sudeste = new QuadTree(se, this.capacidade); 28 | 29 | let so = new Retangulo(x - w/2, y + h/2, w/2, h/2); 30 | this.sudoeste = new QuadTree(so, this.capacidade); 31 | 32 | this.dividida = true; 33 | 34 | } 35 | 36 | inserirPonto(ponto){ 37 | 38 | if(!this.limite.contemPonto(ponto)){ // Checa se o ponto está contido dentro dos limites (fronteiras) do retângulo raiz 39 | return false; 40 | } 41 | 42 | if(this.pontos.length < this.capacidade){ 43 | this.pontos.push(ponto); 44 | return true; 45 | } else{ // Se a capacidade máxima tiver sido atingida 46 | if(!this.dividida){ // A QuadTree não irá se subdividir caso já o tenha feito 47 | this.subdivide(); 48 | } 49 | 50 | // Não checamos a localização do ponto pois ele será checado no começo de cada chamada desses métodos 51 | if(this.nordeste.inserirPonto(ponto)){ 52 | return true; 53 | } else if(this.noroeste.inserirPonto(ponto)){ 54 | return true; 55 | } else if(this.sudeste.inserirPonto(ponto)){ 56 | return true; 57 | } else if(this.sudoeste.inserirPonto(ponto)){ 58 | return true; 59 | }; 60 | } 61 | } 62 | 63 | inserirAlimento(alimento){ 64 | if(!this.limite.contemPonto(alimento)){ // Checa se o alimento está contido dentro dos limites (fronteiras) do retângulo raiz 65 | return false; 66 | } 67 | 68 | if(this.alimentos.length < this.capacidade){ // Se ainda couber alimentos dentro dela 69 | this.alimentos.push(alimento); // Insere o alimento em sua lista 70 | // console.log("alimentos ", this.alimentos); 71 | return true; 72 | } else{ // Se a capacidade máxima de seres vivos tiver sido atingida 73 | if(!this.dividida){ // A QuadTree não irá se subdividir caso já o tenha feito 74 | // console.log("inserirAlimentos", this.alimentos); 75 | this.subdivide(); 76 | // console.log("SUBDIVIDIU - A", this.capacidade); 77 | } 78 | 79 | // Não checamos a localização do alimento pois ela será checada no começo de cada chamada desses métodos 80 | if(this.nordeste.inserirAlimento(alimento)){ 81 | return true; 82 | } else if(this.noroeste.inserirAlimento(alimento)){ 83 | return true; 84 | } else if(this.sudeste.inserirAlimento(alimento)){ 85 | return true; 86 | } else if(this.sudoeste.inserirAlimento(alimento)){ 87 | return true; 88 | }; 89 | } 90 | } 91 | 92 | inserirHerbivoro(herbivoro){ 93 | if(!this.limite.contemPonto(herbivoro)){ // Checa se o herbivoro está contido dentro dos limites (fronteiras) do retângulo raiz 94 | return false; 95 | } 96 | 97 | if(this.herbivoros.length < this.capacidade){ // Se ainda couber herbivoros dentro dela 98 | this.herbivoros.push(herbivoro); // Insere o herbivoro em sua lista 99 | return true; 100 | } else{ // Se a capacidade máxima de seres vivos tiver sido atingida 101 | if(!this.dividida){ // A QuadTree não irá se subdividir caso já o tenha feito 102 | this.subdivide(); 103 | } 104 | 105 | // Não checamos a localização do herbivoro pois ela será checada no começo de cada chamada desses métodos 106 | if(this.nordeste.inserirHerbivoro(herbivoro)){ 107 | return true; 108 | } else if(this.noroeste.inserirHerbivoro(herbivoro)){ 109 | return true; 110 | } else if(this.sudeste.inserirHerbivoro(herbivoro)){ 111 | return true; 112 | } else if(this.sudoeste.inserirHerbivoro(herbivoro)){ 113 | return true; 114 | }; 115 | } 116 | } 117 | 118 | inserirCarnivoro(carnivoro){ 119 | if(!this.limite.contemPonto(carnivoro)){ // Checa se o carnivoro está contido dentro dos limites (fronteiras) do retângulo raiz 120 | return false; 121 | } 122 | 123 | if(this.carnivoros.length < this.capacidade){ // Se ainda couber carnivoros dentro dela 124 | // console.log("DANDO PUSH"); 125 | this.carnivoros.push(carnivoro); // Insere o carnivoro em sua lista 126 | // console.log("carnivoros ", this.carnivoros); 127 | return true; 128 | } else{ // Se a capacidade máxima de seres vivos tiver sido atingida 129 | if(!this.dividida){ // A QuadTree não irá se subdividir caso já o tenha feito 130 | // console.log("inserirCarnivoros", this.carnivoros); 131 | this.subdivide(); 132 | // console.log("SUBDIVIDIU - C", this.capacidade); 133 | } 134 | 135 | // Não checamos a localização do carnivoro pois ela será checada no começo de cada chamada desses métodos 136 | if(this.nordeste.inserirCarnivoro(carnivoro)){ 137 | return true; 138 | } else if(this.noroeste.inserirCarnivoro(carnivoro)){ 139 | return true; 140 | } else if(this.sudeste.inserirCarnivoro(carnivoro)){ 141 | return true; 142 | } else if(this.sudoeste.inserirCarnivoro(carnivoro)){ 143 | return true; 144 | }; 145 | } 146 | } 147 | 148 | procuraPontos(alcance, encontrados){ // alcance é do tipo Retangulo 149 | if(!encontrados){ 150 | encontrados = []; 151 | } 152 | if(!this.limite.intersepta(alcance)){ // Se NÃO se interceptam, não executa o código 153 | return; 154 | } else{ // Se eles se interceptam 155 | for(let p of this.pontos){ // Para os pontos dessa QuadTree 156 | if(alcance.contemPonto(p)){ // Se o ponto pertencer ao retângulo "alcance" 157 | encontrados.push(p); 158 | } 159 | } 160 | 161 | if(this.dividida){ // Se a QuadTree tiver QuadTrees filhas 162 | this.noroeste.procuraPontos(alcance, encontrados); 163 | this.nordeste.procuraPontos(alcance, encontrados); 164 | this.sudoeste.procuraPontos(alcance, encontrados); 165 | this.sudeste.procuraPontos(alcance, encontrados); 166 | } 167 | 168 | return encontrados; 169 | } 170 | } 171 | 172 | procuraAlimentos(circulo, encontrados){ 173 | if(!encontrados){ 174 | encontrados = []; 175 | } 176 | if(!this.limite.interseptaC(circulo)){ // Se NÃO se interceptam, não executa o código 177 | return encontrados; 178 | } else{ // Se eles se interceptam 179 | for(let a of this.alimentos){ // Para os alimentos dessa QuadTree 180 | if(circulo.contemPonto(a)){ // Se o alimento pertencer ao círculo 181 | encontrados.push(a); 182 | } 183 | } 184 | 185 | if(this.dividida){ // Se a QuadTree tiver QuadTrees filhas 186 | this.noroeste.procuraAlimentos(circulo, encontrados); 187 | this.nordeste.procuraAlimentos(circulo, encontrados); 188 | this.sudoeste.procuraAlimentos(circulo, encontrados); 189 | this.sudeste.procuraAlimentos(circulo, encontrados); 190 | } 191 | 192 | return encontrados; 193 | } 194 | } 195 | 196 | procuraHerbivoros(circulo, encontrados){ 197 | if(!encontrados){ 198 | encontrados = []; 199 | } 200 | if(!this.limite.interseptaC(circulo)){ // Se NÃO se interceptam, não executa o código 201 | return encontrados; 202 | } else{ // Se eles se interceptam 203 | for(let h of this.herbivoros){ // Para os herbivoros dessa QuadTree 204 | if(circulo.contemPonto(h)){ // Se o herbivoro pertencer ao círculo 205 | encontrados.push(h); 206 | } 207 | } 208 | 209 | if(this.dividida){ // Se a QuadTree tiver QuadTrees filhas 210 | this.noroeste.procuraHerbivoros(circulo, encontrados); 211 | this.nordeste.procuraHerbivoros(circulo, encontrados); 212 | this.sudoeste.procuraHerbivoros(circulo, encontrados); 213 | this.sudeste.procuraHerbivoros(circulo, encontrados); 214 | } 215 | 216 | return encontrados; 217 | } 218 | } 219 | 220 | procuraCarnivoros(circulo, encontrados){ 221 | if(!encontrados){ 222 | encontrados = []; 223 | } 224 | if(!this.limite.interseptaC(circulo)){ // Se NÃO se interceptam, não executa o código 225 | return encontrados; 226 | } else{ // Se eles se interceptam 227 | // console.log("procuraCarnivoros", this.carnivoros); 228 | for(let c of this.carnivoros){ // Para os carnivoros dessa QuadTree 229 | if(circulo.contemPonto(c)){ // Se o carnivoro pertencer ao círculo 230 | encontrados.push(c); 231 | } 232 | } 233 | 234 | if(this.dividida){ // Se a QuadTree tiver QuadTrees filhas 235 | this.noroeste.procuraCarnivoros(circulo, encontrados); 236 | this.nordeste.procuraCarnivoros(circulo, encontrados); 237 | this.sudoeste.procuraCarnivoros(circulo, encontrados); 238 | this.sudeste.procuraCarnivoros(circulo, encontrados); 239 | } 240 | 241 | return encontrados; 242 | } 243 | } 244 | 245 | desenha(){ 246 | // c.lineWidth = 1; 247 | c.beginPath(); 248 | c.rect(this.limite.x - this.limite.w, this.limite.y - this.limite.h, this.limite.w*2, this.limite.h*2); 249 | c.stroke(); 250 | if(this.dividida){ 251 | this.nordeste.desenha(); 252 | this.noroeste.desenha(); 253 | this.sudeste.desenha(); 254 | this.sudoeste.desenha(); 255 | } 256 | // for(let a of this.alimentos){ 257 | // c.beginPath(); 258 | // c.arc(a.posicao.x, a.posicao.y, 1, 0, 2 * Math.PI); 259 | // c.stroke(); 260 | // } 261 | // for(let h of this.herbivoros){ 262 | // c.beginPath(); 263 | // c.arc(h.posicao.x, h.posicao.y, 1, 0, 2 * Math.PI); 264 | // c.stroke(); 265 | // } 266 | // for(let ca of this.carnivoros){ 267 | // c.beginPath(); 268 | // c.arc(ca.posicao.x, ca.posicao.y, 1, 0, 2 * Math.PI); 269 | // c.stroke(); 270 | // } 271 | } 272 | 273 | 274 | } -------------------------------------------------------------------------------- /public/chart.js: -------------------------------------------------------------------------------- 1 | // Função para retornar o ultimo elemento de um array sem retira-lo de la 2 | Array.prototype.last = function() { 3 | return this[this.length - 1]; 4 | } 5 | 6 | var cnt = 0; 7 | var segundoRepetido = -1; 8 | const chart = document.getElementById("chart") 9 | const chartSecundario = document.getElementById("chartSecundario") 10 | 11 | let historico = new Historico(); 12 | let historicoE = new Historico(); 13 | let historicoD = new Historico(); 14 | 15 | // if(telaDividida) 16 | 17 | function resetChart() { 18 | Plotly.purge(chart); 19 | if(!telaDividida) Plotly.purge(chartSecundario); 20 | } 21 | 22 | function splitChart() { 23 | telaDividida = !telaDividida; 24 | historicoE.clear(); 25 | historicoD.clear(); 26 | resetChart(); 27 | // Remover títulos dos gráficos 28 | removeChartTitle(); 29 | //Construir 30 | buildChart(chartType); 31 | 32 | // Adicionar título aos gráficos 33 | insertChartTitle(); 34 | } 35 | 36 | function insertChartTitle() { 37 | if(telaDividida) { 38 | $(chart).prepend(`
Lado esquerdo
`) 39 | $(chartSecundario).prepend(`
Lado direito
`) 40 | } 41 | } 42 | 43 | function removeChartTitle() { 44 | $(chart).html("") 45 | $(chartSecundario).html("") 46 | } 47 | 48 | function insertNextDataChart() { 49 | // Não deixa inserir dados para segundos repetidos 50 | if(segundos_totais == segundoRepetido) { 51 | return; 52 | } 53 | segundoRepetido = segundos_totais; 54 | // Salva os valores atuais no(s) historico(s) --------------------------------------------- 55 | // Carnivoros 56 | historico.carnivoros.populacao.push(popC.sem_div) 57 | historico.carnivoros.velocidade.push(velMedC.sem_div) 58 | historico.carnivoros.agilidade.push(forcaMedC.sem_div) 59 | historico.carnivoros.raio.push(raioMedC.sem_div) 60 | historico.carnivoros.deteccao.push(raioDetMedC.sem_div) 61 | historico.carnivoros.energia.push(energMedC.sem_div) 62 | historico.carnivoros.gasto.push(taxaEnergMedC.sem_div) 63 | historico.carnivoros.tamanho_medio_ninhada.push(ninhadaMediaC.sem_div) 64 | 65 | // Herbivoros 66 | historico.herbivoros.populacao.push(popH.sem_div) 67 | historico.herbivoros.velocidade.push(velMedH.sem_div) 68 | historico.herbivoros.agilidade.push(forcaMedH.sem_div) 69 | historico.herbivoros.raio.push(raioMedH.sem_div) 70 | historico.herbivoros.deteccao.push(raioDetMedH.sem_div) 71 | historico.herbivoros.energia.push(energMedH.sem_div) 72 | historico.herbivoros.gasto.push(taxaEnergMedH.sem_div) 73 | historico.herbivoros.tamanho_medio_ninhada.push(ninhadaMediaH.sem_div) 74 | 75 | // Segundos 76 | historico.segundos.push(segundos_totais) 77 | 78 | // Outras infos para a análise de dados 79 | historico.taxa_alimentos.push(inputTaxaAlimentos.value) 80 | 81 | if(telaDividida) { 82 | // Historico do lado esquerdo 83 | // Carnivoros 84 | historicoE.carnivoros.populacao.push(popC.esq) 85 | historicoE.carnivoros.velocidade.push(velMedC.esq) 86 | historicoE.carnivoros.agilidade.push(forcaMedC.esq) 87 | historicoE.carnivoros.raio.push(raioMedC.esq) 88 | historicoE.carnivoros.deteccao.push(raioDetMedC.esq) 89 | historicoE.carnivoros.energia.push(energMedC.esq) 90 | historicoE.carnivoros.gasto.push(taxaEnergMedC.esq) 91 | historicoE.carnivoros.tamanho_medio_ninhada.push(ninhadaMediaC.esq) 92 | 93 | // Herbivoros 94 | historicoE.herbivoros.populacao.push(popH.esq) 95 | historicoE.herbivoros.velocidade.push(velMedH.esq) 96 | historicoE.herbivoros.agilidade.push(forcaMedH.esq) 97 | historicoE.herbivoros.raio.push(raioMedH.esq) 98 | historicoE.herbivoros.deteccao.push(raioDetMedH.esq) 99 | historicoE.herbivoros.energia.push(energMedH.esq) 100 | historicoE.herbivoros.gasto.push(taxaEnergMedH.esq) 101 | historicoE.herbivoros.tamanho_medio_ninhada.push(ninhadaMediaH.esq) 102 | 103 | historicoE.segundos.push(segundos_totais) 104 | 105 | historicoE.taxa_alimentos.push(inputTaxaAlimentos.value) 106 | 107 | // Historico do lado direito 108 | // Carnivoros 109 | historicoD.carnivoros.populacao.push(popC.dir) 110 | historicoD.carnivoros.velocidade.push(velMedC.dir) 111 | historicoD.carnivoros.agilidade.push(forcaMedC.dir) 112 | historicoD.carnivoros.raio.push(raioMedC.dir) 113 | historicoD.carnivoros.deteccao.push(raioDetMedC.dir) 114 | historicoD.carnivoros.energia.push(energMedC.dir) 115 | historicoD.carnivoros.gasto.push(taxaEnergMedC.dir) 116 | historicoD.carnivoros.tamanho_medio_ninhada.push(ninhadaMediaC.dir) 117 | 118 | // Herbivoros 119 | historicoD.herbivoros.populacao.push(popH.dir) 120 | historicoD.herbivoros.velocidade.push(velMedH.dir) 121 | historicoD.herbivoros.agilidade.push(forcaMedH.dir) 122 | historicoD.herbivoros.raio.push(raioMedH.dir) 123 | historicoD.herbivoros.deteccao.push(raioDetMedH.dir) 124 | historicoD.herbivoros.energia.push(energMedH.dir) 125 | historicoD.herbivoros.gasto.push(taxaEnergMedH.dir) 126 | historicoD.herbivoros.tamanho_medio_ninhada.push(ninhadaMediaH.dir) 127 | 128 | historicoD.segundos.push(segundos_totais) 129 | 130 | historicoD.taxa_alimentos.push(inputTaxaAlimentos.value) 131 | } 132 | // ------------------------------------------------------------------------------------------ 133 | // Mesma variável de tempo para todos 134 | let arraySeconds = [[segundos_totais], [segundos_totais]]; 135 | 136 | // Identificar qual o gráfico atual 137 | let valores; 138 | let valores2; 139 | 140 | switch(chartType) { 141 | case 1: 142 | if(telaDividida) { 143 | valores = [[historicoE.carnivoros.populacao.last()], [historicoE.herbivoros.populacao.last()]]; 144 | valores2 = [[historicoD.carnivoros.populacao.last()], [historicoD.herbivoros.populacao.last()]]; 145 | } else 146 | valores = [[historico.carnivoros.populacao.last()], [historico.herbivoros.populacao.last()]]; 147 | break; 148 | case 2: 149 | if(telaDividida) { 150 | valores = [[historicoE.carnivoros.velocidade.last()], [historicoE.herbivoros.velocidade.last()]]; 151 | valores2 = [[historicoD.carnivoros.velocidade.last()], [historicoD.herbivoros.velocidade.last()]]; 152 | } else 153 | valores = [[historico.carnivoros.velocidade.last()], [historico.herbivoros.velocidade.last()]]; 154 | break; 155 | case 3: 156 | if(telaDividida) { 157 | valores = [[historicoE.carnivoros.agilidade.last()], [historicoE.herbivoros.agilidade.last()]]; 158 | valores2 = [[historicoD.carnivoros.agilidade.last()], [historicoD.herbivoros.agilidade.last()]]; 159 | } else 160 | valores = [[historico.carnivoros.agilidade.last()], [historico.herbivoros.agilidade.last()]]; 161 | break; 162 | case 4: 163 | if(telaDividida) { 164 | valores = [[historicoE.carnivoros.raio.last()], [historicoE.herbivoros.raio.last()]]; 165 | valores2 = [[historicoD.carnivoros.raio.last()], [historicoD.herbivoros.raio.last()]]; 166 | } else 167 | valores = [[historico.carnivoros.raio.last()], [historico.herbivoros.raio.last()]]; 168 | break; 169 | case 5: 170 | if(telaDividida) { 171 | valores = [[historicoE.carnivoros.deteccao.last()], [historicoE.herbivoros.deteccao.last()]]; 172 | valores2 = [[historicoD.carnivoros.deteccao.last()], [historicoD.herbivoros.deteccao.last()]]; 173 | } else 174 | valores = [[historico.carnivoros.deteccao.last()], [historico.herbivoros.deteccao.last()]]; 175 | break; 176 | case 6: 177 | if(telaDividida) { 178 | valores = [[historicoE.carnivoros.energia.last()], [historicoE.herbivoros.energia.last()]]; 179 | valores2 = [[historicoD.carnivoros.energia.last()], [historicoD.herbivoros.energia.last()]]; 180 | } else 181 | valores = [[historico.carnivoros.energia.last()], [historico.herbivoros.energia.last()]]; 182 | break; 183 | case 7: 184 | if(telaDividida) { 185 | valores = [[historicoE.carnivoros.gasto.last()], [historicoE.herbivoros.gasto.last()]]; 186 | valores2 = [[historicoD.carnivoros.gasto.last()], [historicoD.herbivoros.gasto.last()]]; 187 | } else 188 | valores = [[historico.carnivoros.gasto.last()], [historico.herbivoros.gasto.last()]]; 189 | break; 190 | case 8: 191 | if(telaDividida) { 192 | valores = [[historicoE.carnivoros.tamanho_medio_ninhada.last()], [historicoE.herbivoros.tamanho_medio_ninhada.last()]]; 193 | valores2 = [[historicoD.carnivoros.tamanho_medio_ninhada.last()], [historicoD.herbivoros.tamanho_medio_ninhada.last()]]; 194 | } else 195 | valores = [[historico.carnivoros.tamanho_medio_ninhada.last()], [historico.herbivoros.tamanho_medio_ninhada.last()]]; 196 | } 197 | 198 | Plotly.extendTraces(chart,{y: valores, x: arraySeconds}, [0,1]); 199 | cnt++; 200 | if(cnt >500) { 201 | Plotly.relayout('chart',{ 202 | xaxis: { 203 | range: [cnt-500,cnt] 204 | } 205 | }); 206 | } 207 | 208 | if(telaDividida) { 209 | Plotly.extendTraces(chartSecundario,{y: valores2, x: arraySeconds}, [0,1]); 210 | cnt++; 211 | if(cnt >500) { 212 | Plotly.relayout('chartSecundario',{ 213 | xaxis: { 214 | range: [cnt-500,cnt] 215 | } 216 | }); 217 | } 218 | } 219 | 220 | } 221 | 222 | /*var carnivoros = { 223 | x: [], 224 | y: [], 225 | type: 'scatter', 226 | mode: 'lines', 227 | name: 'Carnívoros', 228 | line: { 229 | color: 'red', 230 | shape: 'spline' 231 | } 232 | }; 233 | 234 | var herbivoros = { 235 | x: [], 236 | y: [], 237 | type: 'scatter', 238 | mode: 'lines', 239 | name: 'Herbívoros', 240 | line: { 241 | color: 'green', 242 | shape: 'spline' 243 | } 244 | }; 245 | 246 | var data = [carnivoros, herbivoros]; 247 | 248 | var layout = { 249 | title: "População", 250 | 251 | xaxis: { 252 | showline: true, 253 | domain: [0], 254 | title: "Segundos", 255 | showgrid: true 256 | }, 257 | yaxis: { 258 | showline: true, 259 | title: "N° Indivíduos", 260 | rangemode: "tozero" 261 | }, 262 | legend: { 263 | orientation: 'h', 264 | traceorder: 'reversed', 265 | x: 0.05, 266 | y: -.3 267 | }, 268 | plot_bgcolor:"#222", 269 | paper_bgcolor:"#222", 270 | font: { 271 | color: '#ddd' 272 | } 273 | 274 | 275 | }; 276 | 277 | Plotly.newPlot('chart', data, layout); */ 278 | 279 | function changeChart(type) { 280 | if(type == chartType || type < 1 || type > 8) { 281 | return; 282 | } 283 | resetChart(); 284 | removeChartTitle(); 285 | buildChart(type); 286 | insertChartTitle(); 287 | chartType = type; 288 | } 289 | 290 | function buildChart(type) { 291 | // ÍNDICE DE QUAL DADO PEGARÁ DO ARRAY DE DADOS (em relação ao carnívoro) 292 | //let indice = 1; // Valores do gráfico 1 para servir de inicializador 293 | let title = "População"; 294 | let yTitle = "N° Indivíduos"; //Título do eixo y 295 | let data = []; 296 | let data2 = []; 297 | 298 | // Puxar o historico do novo tipo de grafico ----------------------------------------------------- 299 | switch (type) { 300 | case 1: // População 301 | if(telaDividida) { 302 | data = [historicoE.carnivoros.populacao, historicoE.herbivoros.populacao]; 303 | data2 = [historicoD.carnivoros.populacao, historicoD.herbivoros.populacao]; 304 | } else 305 | data = [historico.carnivoros.populacao, historico.herbivoros.populacao]; 306 | break; 307 | case 2: // Velocidade 308 | //indice = 3; 309 | title = "Velocidade"; 310 | yTitle = "Velocidade média"; 311 | 312 | if(telaDividida) { 313 | data = [historicoE.carnivoros.velocidade, historicoE.herbivoros.velocidade]; 314 | data2 = [historicoD.carnivoros.velocidade, historicoD.herbivoros.velocidade]; 315 | } else 316 | data = [historico.carnivoros.velocidade, historico.herbivoros.velocidade]; 317 | break; 318 | case 3: // Força 319 | //indice = 5; 320 | title = "Agilidade"; 321 | yTitle = "Agilidade Média"; 322 | 323 | if(telaDividida) { 324 | data = [historicoE.carnivoros.agilidade, historicoE.herbivoros.agilidade]; 325 | data2 = [historicoD.carnivoros.agilidade, historicoD.herbivoros.agilidade]; 326 | } else 327 | data = [historico.carnivoros.agilidade, historico.herbivoros.agilidade]; 328 | break; 329 | case 4: // Raio 330 | //indice = 7; 331 | title = "Raio"; 332 | yTitle = "Raio médio"; 333 | 334 | if(telaDividida) { 335 | data = [historicoE.carnivoros.raio, historicoE.herbivoros.raio]; 336 | data2 = [historicoD.carnivoros.raio, historicoD.herbivoros.raio]; 337 | } else 338 | data = [historico.carnivoros.raio, historico.herbivoros.raio]; 339 | break; 340 | case 5: // Raio deteccao 341 | //indice = 9; 342 | title = "Alcance de detecção"; 343 | yTitle = "Raio de detecção médio"; 344 | 345 | if(telaDividida) { 346 | data = [historicoE.carnivoros.deteccao, historicoE.herbivoros.deteccao]; 347 | data2 = [historicoD.carnivoros.deteccao, historicoD.herbivoros.deteccao]; 348 | } else 349 | data = [historico.carnivoros.deteccao, historico.herbivoros.deteccao]; 350 | break; 351 | case 6: // Energia 352 | //indice = 11; 353 | title = "Energia"; 354 | yTitle = "Nível de energia médio"; 355 | 356 | if(telaDividida) { 357 | data = [historicoE.carnivoros.energia, historicoE.herbivoros.energia]; 358 | data2 = [historicoD.carnivoros.energia, historicoD.herbivoros.energia]; 359 | } else 360 | data = [historico.carnivoros.energia, historico.herbivoros.energia]; 361 | break; 362 | case 7: // Taxa de energia 363 | //indice = 13; 364 | title = "Gasto de energia"; 365 | yTitle = "Taxa de energia média"; 366 | 367 | if(telaDividida) { 368 | data = [historicoE.carnivoros.gasto, historicoE.herbivoros.gasto]; 369 | data2 = [historicoD.carnivoros.gasto, historicoD.herbivoros.gasto]; 370 | } else 371 | data = [historico.carnivoros.gasto, historico.herbivoros.gasto]; 372 | break; 373 | case 8: // Tamanho médio da ninhada 374 | //indice = 15; 375 | title = "Ninhada média"; 376 | yTitle = "Tamanho médio da ninhada"; 377 | 378 | if(telaDividida) { 379 | data = [historicoE.carnivoros.tamanho_medio_ninhada, historicoE.herbivoros.tamanho_medio_ninhada]; 380 | data2 = [historicoD.carnivoros.tamanho_medio_ninhada, historicoD.herbivoros.tamanho_medio_ninhada]; 381 | } else 382 | data = [historico.carnivoros.tamanho_medio_ninhada, historico.herbivoros.tamanho_medio_ninhada]; 383 | } 384 | // ----------------------------------------------------------------------------------------------- 385 | 386 | // INSERE TODO O HISTÓRICO DE DADOS NO GRÁFICO 387 | let carnivoros = { 388 | x: telaDividida? historicoE.segundos:historico.segundos, 389 | y: data[0], 390 | type: 'scatter', 391 | mode: 'lines', 392 | name: 'Carnívoros', 393 | line: { color: 'red', shape: 'spline'} 394 | }; 395 | 396 | let herbivoros = { 397 | x: telaDividida? historicoE.segundos:historico.segundos, 398 | y: data[1], 399 | type: 'scatter', 400 | mode: 'lines', 401 | name: 'Herbívoros', 402 | line: {color: 'green', shape: 'spline'} 403 | }; 404 | 405 | let dataConfig = [carnivoros, herbivoros]; 406 | 407 | var layout = { 408 | title: title, 409 | 410 | xaxis: { 411 | showline: true, 412 | domain: [0], 413 | title: "Segundos", 414 | showgrid: true 415 | }, 416 | yaxis: { 417 | showline: true, 418 | title: yTitle, 419 | rangemode: "tozero" 420 | }, 421 | legend: { 422 | orientation: 'h', 423 | traceorder: 'reversed', 424 | x: 0.05, 425 | y: -.3 426 | }, 427 | plot_bgcolor:"#222", 428 | paper_bgcolor:"#222", 429 | font: { 430 | color: '#ddd' 431 | } 432 | } 433 | 434 | Plotly.newPlot('chart', dataConfig, layout); 435 | 436 | let carnivoros2, herbivoros2, dataConfig2; 437 | if(telaDividida) { 438 | carnivoros2 = { 439 | x: historicoD.segundos, 440 | y: data2[0], 441 | type: 'scatter', 442 | mode: 'lines', 443 | name: 'Carnívoros', 444 | line: { color: 'red', shape: 'spline'} 445 | }; 446 | 447 | herbivoros2 = { 448 | x: historicoD.segundos, 449 | y: data2[1], 450 | type: 'scatter', 451 | mode: 'lines', 452 | name: 'Herbívoros', 453 | line: {color: 'green', shape: 'spline'} 454 | }; 455 | 456 | dataConfig2 = [carnivoros2, herbivoros2]; 457 | 458 | Plotly.newPlot(chartSecundario, dataConfig2, layout); 459 | } 460 | } 461 | 462 | -------------------------------------------------------------------------------- /public/Classes/Organismo.js: -------------------------------------------------------------------------------- 1 | class Organismo{ 2 | static organismos = []; 3 | static n_total_organismos = 0; 4 | static id = 0; 5 | 6 | constructor(x, y, dna, pai = null){ 7 | this.id = Organismo.id++; 8 | this.posicao = new Vetor(x, y); 9 | if(pai){ 10 | this.pai = pai; 11 | } 12 | this.filhos = []; 13 | 14 | this.raio_inicial = dna.raio_inicial; 15 | this.vel_max = dna.vel_max; 16 | this.forca_max = dna.forca_max; 17 | this.cor = dna.cor; 18 | this.raio_deteccao_inicial = dna.raio_deteccao_inicial; 19 | this.intervalo_ninhada = dna.intervalo_ninhada; 20 | this.sexo = dna.sexo; 21 | 22 | // DNA -> Objeto para separar apenas os atributos passados para os descendentes 23 | this.dna = new DNA( 24 | this.raio_inicial, 25 | this.vel_max, 26 | this.forca_max, 27 | this.cor, 28 | this.raio_deteccao_inicial, 29 | this.intervalo_ninhada, 30 | this.sexo 31 | ) 32 | 33 | this.raio = this.raio_inicial; 34 | this.vel = new Vetor(0.0001, 0.0001); 35 | this.acel = new Vetor(0, 0); 36 | var rgb = this.cor.substring(4, this.cor.length - 1).split(","); 37 | this.cor2 = "rgba(" + Math.floor(parseInt(rgb[0]) * 0.4) + "," + Math.floor(parseInt(rgb[1]) * 0.4) + "," + Math.floor(parseInt(rgb[2]) * 0.4) + ")"; 38 | 39 | this.raio_deteccao = this.raio_deteccao_inicial; 40 | this.energia_max = Math.pow(this.raio, 2) * 6; 41 | this.energia_max_fixa = Math.pow(this.raio_inicial * 1.5, 2) * 6; // Usada para obter valores não-variáveis no gráfico 42 | 43 | 44 | // NINHADAS 45 | 46 | // this.energia = this.energia_max * 0.75 47 | if(pai){ 48 | this.energia = (this.energia_max * (0.75 + Math.random() / 4)) / (pai.tamanho_ninhada) * 0.6; // Começa com uma parcela da energia máxima 49 | } else{ 50 | this.energia = this.energia_max * 0.75 51 | } 52 | 53 | this.taxa_gasto_energia; 54 | this.gasto_minimo = 0.0032 * Math.pow(Math.pow(this.raio, 2), 0.75); // Seguindo a lei de Kleiber para a taxa metabólica dos seres vivos 55 | this.taxa_gasto_energia_max = this.gasto_minimo + (Math.pow(this.raio_inicial * 1.5, 2) * Math.pow(this.vel_max, 2)) * 0.00012;; 56 | this.chance_de_reproducao = 0.5; 57 | this.status; 58 | this.qdade_comida = 0; 59 | this.vezes_reproduzidas = 0; 60 | this.segundo_nascimento = segundos_totais; // "segundo" é a variável global 61 | this.tempo_vida = parseInt(geraNumeroPorIntervalo(200, 300)); // tempo de vida do organismo 62 | this.tempo_vivido = 0; 63 | this.tamanho_ninhada; 64 | 65 | // Variáveis de status 66 | this.comendo = false; 67 | this.fugindo = false; 68 | this.vagueando = false; 69 | 70 | // Variável que delimita a distância da borda a partir da qual os organismos começarão a fazer a curva para não bater nas bordas 71 | this.d = 20; 72 | 73 | // variáveis usadas para o método vagueia() 74 | this.d_circulo = 2; 75 | this.raio_circulo = 1; 76 | this.angulo_vagueio = Math.random() * 360; 77 | 78 | // variável para separar os organismos que nasceram antes da divisão da tela dos que nasceram depois 79 | this.antes_da_divisao = false; 80 | this.posicao_fixa_momentanea = new Vetor(x, y); 81 | 82 | Organismo.organismos.push(this); 83 | Organismo.n_total_organismos++; 84 | } 85 | 86 | // Criando um método de reprodução comum a todos os organismos 87 | _reproduzir(){ 88 | return this.dna.mutar(); 89 | } 90 | 91 | // Método para atualizar o estado do organismo 92 | update(){ 93 | this.tempo_vivido = segundos_totais - this.segundo_nascimento; 94 | this.taxa_gasto_energia = (Math.pow(this.raio, 2) * Math.pow(this.vel.mag(), 2)) * 0.0002; // Atualiza de acordo com a velocidade atual 95 | // Taxa de diminuição de energia 96 | if(this.energia > 0){ 97 | this.energia -= (this.taxa_gasto_energia + this.gasto_minimo); 98 | 99 | if(Math.random() < (0.0005 * this.qdade_comida)/10){ // Número baixo pois testa a cada frame. Quando mais comeu, maiores as chances 100 | if(Math.random() <= this.chance_de_reproducao){ 101 | // this.reproduzir(); 102 | // NINHADA 103 | 104 | this.tamanho_ninhada = geraInteiro(this.intervalo_ninhada[0], this.intervalo_ninhada[1] + 1); 105 | for(var i = 0; i < this.tamanho_ninhada; i++){ 106 | if(Math.random() < 0.2){ // Para espaçar os nascimentos 107 | this.reproduzir(); 108 | } 109 | } 110 | } 111 | } 112 | } else{ 113 | this.morre(); 114 | } 115 | 116 | if(this.tempo_vivido >= this.tempo_vida){ // se se passar mais tempo desde o nascimento que o tempo de vida do organismo 117 | this.morre(); 118 | } 119 | 120 | // Delimitação de bordas 121 | if(telaDividida){ 122 | this.criaBordas(true); 123 | } else{ 124 | this.criaBordas(false); 125 | } 126 | 127 | // Atualização da velocidade (soma vetor velocidade com o vetor aceleração) 128 | this.vel.add(this.acel); 129 | // Limita velocidade 130 | this.vel.limit(this.vel_max); 131 | 132 | // console.log("ângulo vel: ", this.vel.headingDegs()); 133 | 134 | // Se existir um proxy, inserir por lá para que seja possível monitorar a mudança de posicao 135 | if(this.proxy) { 136 | this.proxy.add(this.vel) 137 | } else { 138 | // A velocidade altera a posição (assim como a aceleração altera a velocidade) 139 | this.posicao.add(this.vel); 140 | } 141 | // Reseta a aceleração para 0 a cada ciclo 142 | this.acel.mul(0); 143 | 144 | this.display(); 145 | } 146 | 147 | aumentaTamanho(){ 148 | if(this.raio<(this.raio_inicial*1.5)){ 149 | this.raio += 0.05*this.raio; 150 | this.raio_deteccao += 0.03*this.raio_deteccao; 151 | } 152 | this.energia_max = Math.pow(this.raio, 2) * 6 153 | } 154 | 155 | // Método para criar bordas que delimitarão o espaço do organismo 156 | criaBordas(telaDividida){ // telaDividida: boolean 157 | this.delimitaBordas(telaDividida); 158 | this.evitaBordas(telaDividida); 159 | } 160 | 161 | // Método para impedir a passagem dos organismos para fora da tela 162 | delimitaBordas(telaDividida){ 163 | if(telaDividida == true){ 164 | // Delimitação para quem ficou no lado esquerdo 165 | if(this.posicao.x <= universoWidth/2){ 166 | if(this.posicao.x + 2*this.raio > universoWidth / 2) // borda direita (a divisão, ou seja, na metade da tela) 167 | this.vel.x = this.vel.x * -1; // inverte a velocidade x se ultrapassa a borda 168 | 169 | if(this.posicao.x < 0) // borda esquerda 170 | this.vel.x = this.vel.x * -1; 171 | 172 | if(this.posicao.y + this.raio > universoHeight) // borda baixo 173 | this.vel.y = this.vel.y* -1; 174 | 175 | if(this.posicao.y < 0) // borda cima 176 | this.vel.y = this.vel.y * -1; 177 | } else{ // Delimitação para quem ficou no lado direito 178 | if(this.posicao.x + 2*this.raio > universoWidth) // borda direita 179 | this.vel.x = this.vel.x * -1; //inverte a velocidade x se ultrapassa a borda do canvas 180 | 181 | if(this.posicao.x - this.raio < universoWidth / 2) // borda esquerda (a divisão, ou seja, na metade da tela) 182 | this.vel.x = this.vel.x * -1; 183 | 184 | if(this.posicao.y + this.raio > universoHeight) // borda baixo 185 | this.vel.y = this.vel.y* -1; 186 | 187 | if(this.posicao.y < 0) // borda cima 188 | this.vel.y = this.vel.y * -1; 189 | } 190 | 191 | } else{ // se a tela NÃO estiver dividida 192 | if(this.posicao.x + 2*this.raio > universoWidth) // borda direita 193 | this.vel.x = this.vel.x * -1; //inverte a velocidade x se ultrapassa a borda do canvas 194 | 195 | if(this.posicao.x - this.raio < 0) // borda esquerda 196 | this.vel.x = this.vel.x * -1; 197 | 198 | if(this.posicao.y + this.raio > universoHeight) // borda baixo 199 | this.vel.y = this.vel.y* -1; 200 | 201 | if(this.posicao.y < 0) // borda cima 202 | this.vel.y = this.vel.y * -1; 203 | } 204 | 205 | } 206 | 207 | // Método para aplicar força ao organismo que o impeça de continuar a seguir por uma trajetória para fora da tela 208 | evitaBordas(telaDividida){ 209 | var vel_desejada = null; // Esta velocidade será o vetor que dirá para onde o organismo deve ir para não sair da borda 210 | this.perto_da_borda = false; 211 | 212 | if(telaDividida == true){ 213 | // Para quem ficou na parte esquerda 214 | if(this.posicao.x <= universoWidth/2){ 215 | // Borda esquerda 216 | if(this.posicao.x - this.raio < this.d){ // d é um atributo de todo organismo que delimita a distância de uma borda a partir da qual ele começará a manobrar 217 | vel_desejada = new Vetor(this.vel_max, this.vel.y); // Faz sua velocidade ser máxima na direção x (para a direita) 218 | this.perto_da_borda = true; 219 | } 220 | // Borda direita 221 | else if(this.posicao.x + 2*this.raio > universoWidth / 2 - this.d){ // a borda direita é a metade do canvas (na tela dividida) 222 | vel_desejada = new Vetor(-this.vel_max, this.vel.y); // Faz sua velocidade ser máxima na direção -x (para a esquerda) 223 | this.perto_da_borda = true; 224 | } 225 | // Borda de cima 226 | if(this.posicao.y - this.raio < this.d){ 227 | vel_desejada = new Vetor(this.vel.x, this.vel_max); // Faz sua velocidade ser máxima na direção y (para a baixo) 228 | this.perto_da_borda = true; 229 | } 230 | // Borda de baixo 231 | else if(this.posicao.y + this.raio > universoHeight - this.d){ 232 | vel_desejada = new Vetor(this.vel.x, -this.vel_max); // Faz sua velocidade ser máxima na direção -y (para a cima) 233 | this.perto_da_borda = true; 234 | } 235 | } 236 | 237 | // Para quem ficou na parte direita 238 | else{ 239 | // Borda esquerda 240 | if(this.posicao.x - this.raio < universoWidth/2 + this.d){ // a borda esquerda é a metade do canvas (na tela dividida) 241 | vel_desejada = new Vetor(this.vel_max, this.vel.y); // Faz sua velocidade ser máxima na direção x (para a direita) 242 | this.perto_da_borda = true; 243 | } 244 | // Borda direita 245 | else if(this.posicao.x + 2*this.raio > universoWidth - this.d){ 246 | vel_desejada = new Vetor(-this.vel_max, this.vel.y); // Faz sua velocidade ser máxima na direção -x (para a esquerda) 247 | this.perto_da_borda = true; 248 | } 249 | // Borda de cima 250 | if(this.posicao.y < this.d){ 251 | vel_desejada = new Vetor(this.vel.x, this.vel_max); // Faz sua velocidade ser máxima na direção y (para a baixo) 252 | this.perto_da_borda = true; 253 | } 254 | // Borda de baixo 255 | else if(this.posicao.y > universoHeight - this.d){ 256 | vel_desejada = new Vetor(this.vel.x, -this.vel_max); // Faz sua velocidade ser máxima na direção -y (para a cima) 257 | this.perto_da_borda = true; 258 | } 259 | } 260 | 261 | } else{ // se a tela NÃO estiver dividida 262 | // Borda esquerda 263 | if(this.posicao.x - this.raio < this.d){ 264 | vel_desejada = new Vetor(this.vel_max, this.vel.y); // Faz sua velocidade ser máxima na direção x (para a direita) 265 | this.perto_da_borda = true; 266 | } 267 | // Borda direita 268 | else if(this.posicao.x + this.raio > universoWidth - this.d){ 269 | vel_desejada = new Vetor(-this.vel_max, this.vel.y); // Faz sua velocidade ser máxima na direção -x (para a esquerda) 270 | this.perto_da_borda = true; 271 | } 272 | // Borda de cima 273 | if(this.posicao.y - this.raio < this.d){ 274 | vel_desejada = new Vetor(this.vel.x, this.vel_max); // Faz sua velocidade ser máxima na direção y (para a baixo) 275 | this.perto_da_borda = true; 276 | } 277 | // Borda de baixo 278 | else if(this.posicao.y + this.raio> universoHeight - this.d){ 279 | vel_desejada = new Vetor(this.vel.x, -this.vel_max); // Faz sua velocidade ser máxima na direção -y (para a cima) 280 | this.perto_da_borda = true; 281 | } 282 | } 283 | 284 | 285 | if(vel_desejada != null){ // Caso qualquer uma das condições anteriores tenha sido satisfeita 286 | vel_desejada.normalize(); // Normaliza (transforma para ter tamanho 1) o vetor vel_desejada 287 | vel_desejada.mul(this.vel_max); // Multiplica o vetor (que agora tem tamanho 1) pela velocidade máxima 288 | var redirecionamento = vel_desejada.sub(this.vel); // Cria um vetor de força que redirecionará o organismo 289 | redirecionamento.limit(this.forca_max * 100); // Limita essa força com uma folga maior para dar chances dela ser maior que as outras forças atuantes nele 290 | this.aplicaForca(redirecionamento); // Aplica esta força no organismo e a deixa levemente mais forte para ganhar prioridade em relação a outras forças 291 | } 292 | } 293 | 294 | 295 | // Método para aplicar a força que fará o organismo virar na direção do alvo mais próximo de modo natural 296 | aplicaForca(forca){ 297 | // Adiciona a força à aceleração, o que a faz aumentar 298 | // Podemos considerar a massa no cálculo também: A = F / M (não implementado) 299 | this.acel.add(forca); 300 | } 301 | 302 | // Teste para implementação de aprendizado de comportamento 303 | comportamento(bom, ruim){ 304 | 305 | } 306 | 307 | // Método que fará o organismo vaguear por aí quando não está fugindo ou perseguindo 308 | vagueia(){ 309 | // if(!this.fugindo && !this.comendo){ 310 | this.vagueando = true; 311 | this.status = "vagueando"; 312 | // A ideia é criar uma pequena força a cada frame logo à frente do organismo, a uma d dele. 313 | // Desenharemos um círculo à frente do organismo, e o vetor da força de deslocamento partirá do centro 314 | // do círculo e terá o tamanho de seu raio. Assim, quanto maior o círculo, maior a força. 315 | // A fim de sabermos qual é a frente do organismo, utilizaremos o vetor velocidade para nos auxiliar, 316 | // já que ele está sempre apontando na direção do movimento do organismo. 317 | 318 | // CRIANDO O CÍRCULO 319 | var centro_circulo = new Vetor(0, 0); // Criamos um vetor que representará a distância do organismo ao centro do círculo 320 | centro_circulo = this.vel.copy(); // Isso é para que o círculo esteja exatamente à frente do organismo (como explicado um pouco acima) 321 | centro_circulo.normalize(); // Normalizamos o vetor, ou seja, seu tamanho agora é 1 (e não mais o tamanho do vetor velocidade, como era na linha acima) 322 | centro_circulo.mul(this.d_circulo); // A variável d_circulo é uma constante definida globalmente, e guarda o valor da distância do centro do círculo 323 | 324 | // CRIANDO A FORÇA DE DESLOCAMENTO 325 | var deslocamento = new Vetor(0, -1); 326 | deslocamento.mul(this.raio_circulo); // A força terá o tamanho do raio do círculo 327 | // Mudando a direção da força randomicamente 328 | deslocamento.rotateDegs(this.angulo_vagueio); // Rotaciona a força em angulo_vagueio (variável definida no construtor) 329 | // Mudando ligeiramente o valor de angulo_vagueio para que ele mude pouco a pouco a cada frame 330 | this.angulo_vagueio += Math.random() * 30 - 15; // Muda num valor entre -15 e 15 331 | 332 | // CRIANDO A FORÇA DE VAGUEIO 333 | // A força de vagueio pode ser pensada como um vetor que sai da posição do organismo e vai até um ponto 334 | // na circunferência do círculo que criamos 335 | // Agora que os vetores do centro do círculo e da força de deslocamento foram criados, basta somá-los 336 | // para criar a força de vagueio 337 | var forca_vagueio = new Vetor(0, 0); 338 | forca_vagueio = centro_circulo.add(deslocamento); 339 | 340 | if(this.comendo || this.fugindo){ // Diminui a força de vagueio quando vai comer ou fugir para dar prioridade a estas tarefas 341 | forca_vagueio.mul(0.03); 342 | } 343 | this.aplicaForca(forca_vagueio.mul(0.2)); 344 | // } 345 | } 346 | 347 | // Método que calcula a força de redirecionamento em direção a um alvo 348 | // REDIRECIONAMENTO = VELOCIDADE DESEJADA - VELOCIDADE 349 | persegue(alvo){ 350 | alvo.fugindo = true; 351 | // O vetor da velocidade desejada é o vetor de posição do alvo menos o da própria posição 352 | var vel_desejada = alvo.posicao.subNew(this.posicao); // Um vetor apontando da localização dele para o alvo 353 | // Amplia a velocidade desejada para a velocidade máxima 354 | vel_desejada.setMag(this.vel_max); 355 | 356 | // Redirecionamento = velocidade desejada - velocidade 357 | var redirecionamento = vel_desejada.subNew(this.vel); 358 | redirecionamento.limit(this.forca_max); // Limita o redirecionamento para a força máxima 359 | 360 | // Soma a força de redirecionamento à aceleração 361 | this.aplicaForca(redirecionamento); 362 | } 363 | 364 | // Método de comportamento reprodutivo sexuado para procurar parceiros próximos 365 | procuraParceiros(){ 366 | 367 | } 368 | 369 | // Método de comportamento reprodutivo sexuado para se aproximar do parceiro encontrado 370 | aproximaDoParceiro(){ 371 | // CHAMAR AQUI DENTRO O MÉTODO combinaDnas() 372 | } 373 | 374 | // Método de comportamento reprodutivo sexuado para randomicamente escolher genes do pai e da mãe 375 | combinaDnas(parceiro){ 376 | var dnaFilho = []; 377 | 378 | // Raio inicial 379 | if(Math.random() < 0.5){ 380 | dnaFilho.push(this.dna.raio_inicial) 381 | } else{ 382 | dnaFilho.push(parceiro.dna.raio_inicial) 383 | } 384 | 385 | // Velocidade máxima 386 | if(Math.random() < 0.5){ 387 | dnaFilho.push(this.dna.vel_max) 388 | } else{ 389 | dnaFilho.push(parceiro.dna.vel_max) 390 | } 391 | 392 | // Força máxima 393 | if(Math.random() < 0.5){ 394 | dnaFilho.push(this.dna.forca_max) 395 | } else{ 396 | dnaFilho.push(parceiro.dna.forca_max) 397 | } 398 | 399 | // Cor 400 | if(Math.random() < 0.5){ 401 | dnaFilho.push(this.dna.cor) 402 | } else{ 403 | dnaFilho.push(parceiro.dna.cor) 404 | } 405 | 406 | // Raio detecção inicial 407 | if(Math.random() < 0.5){ 408 | dnaFilho.push(this.dna.raio_deteccao_inicial) 409 | } else{ 410 | dnaFilho.push(parceiro.dna.raio_deteccao_inicial) 411 | } 412 | 413 | // Intervalo ninhada 414 | if(Math.random() < 0.5){ 415 | dnaFilho.push(this.dna.intervalo_ninhada) 416 | } else{ 417 | dnaFilho.push(parceiro.dna.intervalo_ninhada) 418 | } 419 | 420 | // Sexo 421 | if(Math.random() < 0.5){ 422 | dnaFilho.push(this.dna.sexo) 423 | } else{ 424 | dnaFilho.push(parceiro.dna.sexo) 425 | } 426 | 427 | var dna_filho = new DNA(dnaFilho[0], dnaFilho[1], dnaFilho[2], dnaFilho[3], 428 | dnaFilho[4], dnaFilho[5], dnaFilho[6]) 429 | 430 | return dna_filho; 431 | } 432 | 433 | estaMorto(){ 434 | return this.energia <= 0; 435 | } 436 | 437 | remove(lista) { 438 | var what, a = arguments, L = a.length, indice; 439 | while (L > 1 && lista.length) { 440 | what = a[--L]; 441 | while ((indice = lista.indexOf(what)) !== -1) { 442 | lista.splice(indice, 1); 443 | } 444 | } 445 | return lista; 446 | } 447 | 448 | checaId(id){ 449 | return (id == this.id); 450 | } 451 | 452 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Evolv-e 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 38 | 42 | 43 | 44 | 45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 | 53 | 54 | 57 |
58 | 00:00:00:00 59 |
60 | 61 | 64 | 65 | 68 | 69 |
70 |
71 | 72 | 73 | 74 |
75 | 76 |
77 | 78 |
79 |
80 | Simulação 81 | 84 |
85 |
86 | 87 |
88 |
89 | 90 | 91 | 92 | 93 | 94 | 95 |
96 | 97 | 99 | 100 | 101 | 102 | 103 | 105 | 106 | 107 | 108 | 109 | 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 | 162 |
163 | 164 |
165 |
166 |
167 |
168 | 169 | 170 | 171 | 216 | 217 | 218 | 219 | 246 | 247 | 248 |
249 |
250 | 251 | 252 |
253 | 262 |
263 | 264 | 265 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | -------------------------------------------------------------------------------- /public/index.js: -------------------------------------------------------------------------------- 1 | //if(innerWidth<=425){ 2 | var tela ={width: innerWidth, height: innerHeight - 8} 3 | //}else{ 4 | //var tela = {width: innerWidth - 500, height: innerHeight - 8} 5 | //} 6 | const canvas = document.querySelector("canvas"); 7 | canvas.width = tela.width; 8 | canvas.height = tela.height; 9 | 10 | const c = canvas.getContext('2d'); 11 | 12 | 13 | 14 | 15 | // ---------------------------------------ZOOM IN / PANNING-------------------------------------------------- 16 | 17 | var tamanhoUniverso = 3; 18 | 19 | var universoWidth = canvas.width * tamanhoUniverso; 20 | var universoHeight = canvas.height * tamanhoUniverso; 21 | 22 | 23 | trackTransforms(c) 24 | 25 | 26 | function redraw(){ 27 | // Clear the entire canvas 28 | var p1 = c.transformedPoint(0,0); 29 | var p2 = c.transformedPoint(canvas.width,canvas.height); 30 | c.clearRect(p1.x,p1.y,p2.x-p1.x,p2.y-p1.y); 31 | 32 | c.save(); 33 | if (!CanvasRenderingContext2D.prototype.resetTransform) { 34 | CanvasRenderingContext2D.prototype.resetTransform = function() { 35 | this.setTransform(1, 0, 0, 1, 0, 0); 36 | }; 37 | } 38 | // c.setTransform(1,0,0,1,0,0); 39 | c.clearRect(0,0,universoWidth,universoHeight); 40 | c.restore(); 41 | } 42 | // redraw(); 43 | 44 | var lastX=canvas.width/2, lastY=canvas.height/2; 45 | 46 | var dragStart,dragged; 47 | 48 | canvas.addEventListener('mousedown',function(evt){ 49 | if(evt.button == 1){ 50 | document.body.style.mozUserSelect = document.body.style.webkitUserSelect = document.body.style.userSelect = 'none'; 51 | lastX = evt.offsetX || (evt.pageX - canvas.offsetLeft); 52 | lastY = evt.offsetY || (evt.pageY - canvas.offsetTop); 53 | dragStart = c.transformedPoint(lastX,lastY); 54 | dragged = false; 55 | } 56 | },false); 57 | 58 | 59 | canvas.addEventListener('mousemove',function(evt){ 60 | lastX = evt.offsetX || (evt.pageX - canvas.offsetLeft); 61 | lastY = evt.offsetY || (evt.pageY - canvas.offsetTop); 62 | dragged = true; 63 | if (dragStart){ 64 | var pt = c.transformedPoint(lastX,lastY); 65 | c.translate(pt.x-dragStart.x,pt.y-dragStart.y); 66 | redraw(); 67 | } 68 | desenhaTudo(); 69 | },false); 70 | 71 | canvas.addEventListener('mouseup',function(evt){ 72 | dragStart = null; 73 | },false); 74 | 75 | var scaleFactor = 1.05; 76 | 77 | var zoom = function(clicks){ 78 | var pt = c.transformedPoint(lastX,lastY); 79 | c.translate(pt.x,pt.y); 80 | var factor = Math.pow(scaleFactor,clicks); 81 | c.scale(factor,factor); 82 | c.translate(-pt.x,-pt.y); 83 | redraw(); 84 | desenhaTudo(); 85 | } 86 | 87 | 88 | var handleScroll = function(evt){ 89 | var delta = evt.wheelDelta ? evt.wheelDelta/40 : evt.detail ? -evt.detail : 0; 90 | if (delta){ 91 | zoom(delta); 92 | } 93 | return evt.preventDefault() && false; 94 | }; 95 | 96 | canvas.addEventListener('DOMMouseScroll',handleScroll,false); 97 | canvas.addEventListener('mousewheel',handleScroll,false); 98 | 99 | 100 | // Adds c.getTransform() - returns an SVGMatrix 101 | // Adds c.transformedPoint(x,y) - returns an SVGPoint 102 | function trackTransforms(c){ 103 | var svg = document.createElementNS("http://www.w3.org/2000/svg",'svg'); 104 | var xform = svg.createSVGMatrix(); 105 | c.getTransform = function(){ return xform; }; 106 | 107 | var savedTransforms = []; 108 | var save = c.save; 109 | c.save = function(){ 110 | savedTransforms.push(xform.translate(0,0)); 111 | return save.call(c); 112 | }; 113 | 114 | var restore = c.restore; 115 | c.restore = function(){ 116 | xform = savedTransforms.pop(); 117 | return restore.call(c); 118 | }; 119 | 120 | var scale = c.scale; 121 | c.scale = function(sx,sy){ 122 | xform = xform.scaleNonUniform(sx,sy); 123 | return scale.call(c,sx,sy); 124 | }; 125 | 126 | var rotate = c.rotate; 127 | c.rotate = function(radians){ 128 | xform = xform.rotate(radians*180/Math.PI); 129 | return rotate.call(c,radians); 130 | }; 131 | 132 | var translate = c.translate; 133 | c.translate = function(dx,dy){ 134 | xform = xform.translate(dx,dy); 135 | return translate.call(c,dx,dy); 136 | }; 137 | 138 | var transform = c.transform; 139 | c.transform = function(a,b,c,d,e,f){ 140 | var m2 = svg.createSVGMatrix(); 141 | m2.a=a; m2.b=b; m2.c=c; m2.d=d; m2.e=e; m2.f=f; 142 | xform = xform.multiply(m2); 143 | return transform.call(c,a,b,c,d,e,f); 144 | }; 145 | 146 | var setTransform = c.setTransform; 147 | c.setTransform = function(a,b,c,d,e,f){ 148 | xform.a = a; 149 | xform.b = b; 150 | xform.c = c; 151 | xform.d = d; 152 | xform.e = e; 153 | xform.f = f; 154 | return setTransform.call(c,a,b,c,d,e,f); 155 | }; 156 | 157 | var pt = svg.createSVGPoint(); 158 | c.transformedPoint = function(x,y){ 159 | pt.x=x; pt.y=y; 160 | return pt.matrixTransform(xform.inverse()); 161 | } 162 | } 163 | 164 | 165 | 166 | // ------------------------------------------------------------------------------------------------------------------ 167 | 168 | 169 | 170 | 171 | 172 | var fome_c = 0.8; // porcentagem da energia máxima acima da qual eles não comerão 173 | var fome_h = 0.8; // porcentagem da energia máxima acima da qual eles não comerão 174 | 175 | var mudarGrafico = false; 176 | 177 | // Variáveis para o gráfico (herbívoro) 178 | var popH; 179 | var velMedH; 180 | var forcaMedH; 181 | var raioMedH; 182 | var raioDetMedH; 183 | var energMedH; 184 | var taxaEnergMedH; 185 | 186 | // Variáveis para o gráfico (carnívoro) 187 | var popC; 188 | var velMedC; 189 | var forcaMedC; 190 | var raioMedC; 191 | var raioDetMedC; 192 | var energMedC; 193 | var taxaEnergMedC; 194 | 195 | // Variáveis para alterações nas mutações 196 | // var probabilidade_mutacao = labelProb; // chances de cada gene (atributo) sofrer mutação 197 | var magnitude_mutacao = 0.1; // magnitude da mutação (o quanto vai variar) 198 | 199 | var lado_direito_vazio = true; 200 | var lado_esquerdo_vazio = true; 201 | 202 | // QuadTree 203 | let retanguloCanvas = new Retangulo(universoWidth/2, universoHeight/2, universoWidth/2, universoHeight/2); 204 | 205 | var popover_id = 1; 206 | 207 | // Configuracoes dos organismos editados 208 | var conf_c; 209 | var conf_h; 210 | 211 | 212 | 213 | // --------------------------------------------------------------------------------------- 214 | // FUNÇÕES 215 | // --------------------------------------------------------------------------------------- 216 | 217 | function criaUniverso(tamanhoUniverso){ 218 | universoWidth = canvas.width * tamanhoUniverso; 219 | universoHeight = canvas.height * tamanhoUniverso; 220 | } 221 | 222 | function verificaViesMutacoes(valor, iteracoes){ 223 | var menor = 0; 224 | var maior = 0; 225 | var igual = 0; 226 | var novoValor = 0; 227 | for(var i = 0; i < iteracoes; i++){ 228 | novoValor = newMutacao(valor) 229 | if(novoValor > valor){ 230 | maior++; 231 | } else if(novoValor < valor){ 232 | menor++; 233 | } else{ 234 | igual++; 235 | } 236 | } 237 | 238 | console.log("Maior: " + ((maior * 100)/iteracoes) + "%") 239 | console.log("Menor: " + ((menor * 100)/iteracoes) + "%") 240 | console.log("Igual: " + ((igual * 100)/iteracoes) + "%") 241 | console.log("Mutações: " + (((maior + menor) * 100)/iteracoes) + "%") 242 | } 243 | 244 | function verificaViesMutacoesNinhada(ninhada_min, ninhada_max, iteracoes){ 245 | var menor = 0; 246 | var maior = 0; 247 | var igual = 0; 248 | var ninhada_media = (ninhada_min + ninhada_max) / 2; 249 | for(var i = 0; i < iteracoes; i++){ 250 | novoIntervalo = mutacaoNinhada(ninhada_min, ninhada_max) 251 | nova_ninhada_media = (novoIntervalo[0] + novoIntervalo[1]) / 2 252 | if(nova_ninhada_media > ninhada_media){ 253 | maior++; 254 | } else if(nova_ninhada_media < ninhada_media){ 255 | menor++; 256 | } else{ 257 | igual++; 258 | } 259 | } 260 | 261 | console.log("Maior: " + ((maior * 100)/iteracoes) + "%") 262 | console.log("Menor: " + ((menor * 100)/iteracoes) + "%") 263 | console.log("Igual: " + ((igual * 100)/iteracoes) + "%") 264 | console.log("Mutações: " + (((maior + menor) * 100)/iteracoes) + "%") 265 | } 266 | 267 | // Função para não haver a necessidade de dar despausa() e pausa() quando é preciso redesenhar os elementos sem dar play em animate() 268 | function desenhaTudo(){ 269 | Alimento.alimentos.forEach(a => { 270 | a.display(); 271 | }) 272 | Organismo.organismos.forEach(o => { 273 | o.display(); 274 | }) 275 | } 276 | 277 | function exportToCsv(filename, rows) { 278 | var processRow = function (row) { 279 | var finalVal = ''; 280 | for (var j = 0; j < row.length; j++) { 281 | var innerValue = row[j] === null ? '' : row[j].toString(); 282 | if (row[j] instanceof Date) { 283 | innerValue = row[j].toLocaleString(); 284 | }; 285 | var result = innerValue.replace(/""/g, '""""'); 286 | result = result.replace(".", ",") 287 | if (result.search(/("|,|\n)/g) >= 0) 288 | result = '"' + result + '"'; 289 | if (j > 0) 290 | finalVal += ';'; 291 | finalVal += result; 292 | } 293 | return finalVal + '\n'; 294 | }; 295 | 296 | var csvFile = ''; 297 | for (var i = 0; i < rows.length; i++) { 298 | csvFile += processRow(rows[i]); 299 | } 300 | 301 | var blob = new Blob([csvFile], { type: 'text/csv;charset=utf-8;' }); 302 | if (navigator.msSaveBlob) { // IE 10+ 303 | navigator.msSaveBlob(blob, filename); 304 | } else { 305 | var link = document.createElement("a"); 306 | if (link.download !== undefined) { // feature detection 307 | // Browsers that support HTML5 download attribute 308 | var url = URL.createObjectURL(blob); 309 | link.setAttribute("href", url); 310 | link.setAttribute("download", filename); 311 | link.style.visibility = 'hidden'; 312 | document.body.appendChild(link); 313 | link.click(); 314 | document.body.removeChild(link); 315 | } 316 | } 317 | } 318 | 319 | 320 | function criaObjetos(n_carnivoros, n_herbivoros, n_alimentos){ 321 | for(var i = 0; i < n_carnivoros; i++){ 322 | var x =(Math.random() * (universoWidth - 50) + 25); 323 | var y = (Math.random() * (universoHeight - 50) + 25); 324 | geraCarnivoro(x,y); 325 | } 326 | for(var i = 0; i < n_herbivoros; i++){ 327 | var x =(Math.random() * (universoWidth - 50) + 25); 328 | var y = (Math.random() * (universoHeight - 50) + 25); 329 | geraHerbivoro(x,y); 330 | } 331 | for(var i = 0; i < n_alimentos; i++){ 332 | var x =(Math.random() * (universoWidth - 50) + 25); 333 | var y = (Math.random() * (universoHeight - 50) + 25); 334 | geraAlimento(x,y); 335 | } 336 | } 337 | 338 | function destroiObjetos(){ 339 | Carnivoro.carnivoros.length = 0; 340 | Herbivoro.herbivoros.length = 0; 341 | Alimento.alimentos.length = 0; 342 | // mudaIntervaloAlimentos(1001); 343 | } 344 | 345 | 346 | // cria mais alimentos ao longo do tempo 347 | // a função setInterval() permite que ele chame o loop a cada x milisegundos 348 | var intervaloTaxaAlimentos; 349 | 350 | // variáveis de auxílio para a implementação da divisão de tela 351 | var checkbox_divisao = document.getElementById('divisao'); 352 | var telaDividida; 353 | var limitador_de_loop = 0; 354 | 355 | function geraAlimento(x,y){ 356 | var raio = geraNumeroPorIntervalo(1, 2); 357 | return new Alimento(x, y, raio); 358 | } 359 | 360 | function geraCarnivoro(x,y){ // função para poder adicionar mais carnívoros manualmente 361 | var raio_inicial = geraNumeroPorIntervalo(3, 8); 362 | var vel_max = geraNumeroPorIntervalo(1, 2.2); 363 | var forca_max = geraNumeroPorIntervalo(0.01, 0.05); 364 | var cor = geraCor(); 365 | var raio_deteccao_inicial = geraNumeroPorIntervalo(40, 120); 366 | var ninhada_min = geraInteiro(1, 1); 367 | var ninhada_max = ninhada_min + geraInteiro(1, 8); 368 | var intervalo_ninhada = [ninhada_min, ninhada_max]; 369 | var sexo; 370 | 371 | if(Math.random() < 0.5){ 372 | sexo = 'XX' 373 | } else{ 374 | sexo = 'XY' 375 | } 376 | 377 | if(conf_c) { 378 | raio_inicial = conf_c.raio_inicial; 379 | vel_max = conf_c.vel_max; 380 | forca_max = conf_c.forca_max; 381 | cor = conf_c.cor; 382 | intervalo_ninhada = conf_c.intervalo_ninhada 383 | sexo = conf_c.sexo 384 | } 385 | 386 | var dna = new DNA( 387 | raio_inicial, 388 | vel_max, 389 | forca_max, 390 | cor, 391 | raio_deteccao_inicial, 392 | intervalo_ninhada, 393 | sexo 394 | ) 395 | 396 | return new Carnivoro( 397 | x, y, dna 398 | ); 399 | } 400 | 401 | 402 | function geraHerbivoro(x,y){ // função para poder adicionar mais herbivoros manualmente 403 | var raio_inicial = geraNumeroPorIntervalo(3, 8); 404 | var vel_max = geraNumeroPorIntervalo(1, 2.2); 405 | var forca_max = geraNumeroPorIntervalo(0.01, 0.05); 406 | var cor = geraCor(); 407 | var raio_deteccao_inicial = geraNumeroPorIntervalo(40, 120); 408 | var ninhada_min = geraInteiro(1, 1); 409 | var ninhada_max = ninhada_min + geraInteiro(1, 8); 410 | var intervalo_ninhada = [ninhada_min, ninhada_max]; 411 | var sexo; 412 | 413 | if(Math.random() < 0.5){ 414 | sexo = 'XX' 415 | } else{ 416 | sexo = 'XY' 417 | } 418 | 419 | if(conf_h) { 420 | raio_inicial = conf_h.raio_inicial; 421 | vel_max = conf_h.vel_max; 422 | forca_max = conf_h.forca_max; 423 | cor = conf_h.cor; 424 | intervalo_ninhada = conf_h.intervalo_ninhada; 425 | sexo = conf_h.sexo; 426 | } 427 | 428 | var dna = new DNA( 429 | raio_inicial, 430 | vel_max, 431 | forca_max, 432 | cor, 433 | raio_deteccao_inicial, 434 | intervalo_ninhada, 435 | sexo 436 | ) 437 | 438 | return new Herbivoro( 439 | x, y, dna 440 | ); 441 | } 442 | 443 | 444 | function geraCor(){ 445 | // variáveis para a geração de cores 446 | var r = Math.floor(Math.random() * 256); 447 | var g = Math.floor(Math.random() * 256); 448 | var b = Math.floor(Math.random() * 256); 449 | var cor = "rgb(" + r + "," + g + "," + b + ")"; 450 | 451 | return cor; 452 | } 453 | 454 | function hexToRgb(hex) { 455 | let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 456 | return result ? 457 | "rgb(" 458 | + parseInt(result[1], 16) + "," 459 | + parseInt(result[2], 16) + "," 460 | + parseInt(result[3], 16) 461 | + ")" 462 | : null; 463 | } 464 | 465 | function rgbToHex(rgb) { 466 | let result = /^rgb\(([\d]{1,3}),([\d]{1,3}),([\d]{1,3})\)$/i.exec(rgb) 467 | if(!result) return null; 468 | 469 | let r = parseInt(result[1]).toString(16) 470 | let g = parseInt(result[2]).toString(16) 471 | let b = parseInt(result[3]).toString(16) 472 | 473 | return `#${r.length<2? "0"+r:r}${g.length<2? "0"+g:g}${b.length<2? "0"+b:b}` 474 | } 475 | 476 | function corMutacao(estilo) { 477 | if(Math.random() < probabilidade_mutacao){ // Quanto menor for probabilidade_mutacao, menor será a chance da mutação ocorrer 478 | let cores = estilo.substring(4, estilo.length - 1) // remover os caracteres de texto. ex: "rgb(256,20,40)" 479 | .split(',') // retornar um array com os elementos separados por virgula. ex: 256,20,40 480 | .map(function(cor) { //pegar cada elemento do array e fazer os cálculos a seguir 481 | cor = parseInt(cor); 482 | let operacao = ""; 483 | let p = Math.random(); 484 | 485 | if(cor <= 10) { //para não gerar números negativos 486 | operacao = "adicao" 487 | } else if(cor >= 246) { //para não gerar valores maiores que 256 488 | operacao = "subtracao" 489 | 490 | } else { //randomiza se vai ser add ou subtraido valores caso a cor estiver entre 10 e 246 491 | if(Math.random() < 0.5) { 492 | operacao = "adicao" 493 | } else { 494 | operacao = "subtracao" 495 | } 496 | } 497 | 498 | if(operacao == "adicao") { 499 | if(p < 0.002){ // Há 0.2% de chance de a mutação ser grande 500 | return Math.ceil(cor + cor * (Math.random() * magnitude_mutacao * 10)); 501 | } else if(p < 0.008){ // Há 0.6% de chance (0.8% - o 0.2% do if anterior) de a mutação ser razoavelmente grande 502 | return Math.ceil(cor + cor * (Math.random() * magnitude_mutacao * 4)); 503 | } else if(p < 0.028){ // Há 2% de chance (2.8% - o 0.8% do if anterior) de a mutação ser razoável 504 | return Math.ceil(cor + cor * (Math.random() * magnitude_mutacao * 2)); 505 | } else{ 506 | // return cor + Math.ceil(Math.random() * 10) 507 | return Math.ceil(cor + cor * (Math.random() * magnitude_mutacao)); 508 | } 509 | 510 | } else { //subtração 511 | if(p < 0.002){ // Há 0.2% de chance de a mutação ser grande 512 | return Math.ceil(cor - cor * (Math.random() * magnitude_mutacao * 10)); 513 | } else if(p < 0.008){ // Há 0.6% de chance (0.8% - o 0.2% do if anterior) de a mutação ser razoavelmente grande 514 | return Math.ceil(cor - cor * (Math.random() * magnitude_mutacao * 4)); 515 | } else if(p < 0.028){ // Há 2% de chance (2.8% - o 0.8% do if anterior) de a mutação ser razoável 516 | return Math.ceil(cor - cor * (Math.random() * magnitude_mutacao * 2)); 517 | } else{ 518 | return Math.ceil(cor - cor * (Math.random() * magnitude_mutacao)); 519 | } 520 | } 521 | }); 522 | 523 | // console.log("MUTAÇÃO DE COR"); 524 | return `rgb(${cores[0]},${cores[1]},${cores[2]})` 525 | } else{ 526 | return estilo; 527 | } 528 | } 529 | 530 | function newMutacao(valor) {// exemplo: valor = 20; magnitude_mutacao = 0.05 || 5% 531 | if(Math.random() < probabilidade_mutacao){ // Quanto menor for probabilidade_mutacao, menor será a chance da mutação ocorrer 532 | let p = Math.random(); 533 | let variacao = valor * magnitude_mutacao; // variacao = 20 * 0.05 = 1, ou seja, poderá variar de +1 a -1 no resultado 534 | if(p < 0.001){ // Há 0.1% de chance de a mutação ser bem grande 535 | variacao *= 10; 536 | } else if(p < 0.003){ // Há 0.2% de chance (0.3% - 0.1% do if anterior) de a mutação ser grande 537 | variacao *= 6; 538 | } else if(p < 0.008){ /// Há 0.5% de chance (0.8% - o 0.3% do if anterior) de a mutação ser razoavelmente grande 539 | variacao *= 3.5; 540 | } else if(p < 0.028){ // Há 2% de chance (2.8% - o 0.8% do if anterior) de a mutação ser razoável 541 | variacao *= 2; 542 | } 543 | 544 | let minimo = valor - variacao; // minimo = 20 - 1 = 19. Para que não precise sub-dividir o return em adição ou subtração 545 | variacao *= 2 // puxo o ponto de referência para o menor valor possível. Logo, o resultado variará de 546 | // 0 a +2, afinal a distância de 1 até -1 é 2. 547 | if(minimo <= 0) { 548 | minimo = valor * 0.01; // Se a mutação diminuir o valor para menos que 0, ela será simplesmente muito pequena 549 | } 550 | // console.log("MUTAÇÃO"); 551 | return minimo + Math.random() * variacao; // 19 + Math.randon() * 2. O resultado estará entre o intervalo [19, 21] 552 | } else{ // Caso não ocorra mutação, retorna o valor original 553 | return valor; 554 | } 555 | } 556 | 557 | function mutacaoNinhada(ninhada_min, ninhada_max) { 558 | if(Math.random() < probabilidade_mutacao){ // Quanto menor for probabilidade_mutacao, menor será a chance da mutação ocorrer 559 | let variacao_ninhada_min = geraInteiro(0, 2 + Math.floor(magnitude_mutacao * 10)); 560 | let variacao_ninhada_max = geraInteiro(0, 2 + Math.floor(magnitude_mutacao * 10)); 561 | 562 | if(Math.random() >= 0.5) { // Soma 563 | ninhada_min += variacao_ninhada_min; 564 | ninhada_max += variacao_ninhada_max; 565 | } else{ // Subtrai 566 | ninhada_min -= variacao_ninhada_min; 567 | ninhada_max -= variacao_ninhada_max; 568 | } 569 | 570 | if(ninhada_min <= 0) { 571 | ninhada_min = 0; 572 | } 573 | if(ninhada_max <= ninhada_min) { 574 | ninhada_max = ninhada_min + 1; 575 | } 576 | } 577 | 578 | return [ninhada_min, ninhada_max]; 579 | } 580 | 581 | 582 | 583 | function geraNumeroPorIntervalo(min, max) { 584 | let delta = max - min; // exemplo: 4000 e 6000. 6000 - 4000 = 2000 585 | return parseFloat((Math.random() * delta + min).toFixed(4)); // Math.random() * 2000 + 4000 586 | } 587 | 588 | function criaAlimentosGradativo(){ 589 | if(!pausado){ // Para de criar alimentos enquanto a simulação estiver pausada 590 | if(telaDividida){ 591 | if(lado_esquerdo_vazio){ // Se não houver população no lado esquerdo, não gerará alimentos lá 592 | var x = geraNumeroPorIntervalo(universoWidth/2 + 31, universoWidth - 31); 593 | var y = Math.random() * (universoHeight - 62) + 31; 594 | var raio = Math.random() * 1.5 + 1; 595 | 596 | if(Alimento.alimentos.length < 3000){ // Limitador para não sobrecarregar a simulação 597 | new Alimento(x, y, raio); 598 | } 599 | } 600 | if(lado_direito_vazio){ // Se não houver população no lado direito, não gerará alimentos lá 601 | var x = geraNumeroPorIntervalo(31, universoWidth/2 - 31); 602 | var y = Math.random() * (universoHeight - 62) + 31; 603 | var raio = Math.random() * 1.5 + 1; 604 | 605 | if(Alimento.alimentos.length < 3000){ // Limitador para não sobrecarregar a simulação 606 | new Alimento(x, y, raio); 607 | } 608 | } 609 | if(!lado_direito_vazio && !lado_esquerdo_vazio){ 610 | var x = Math.random() * (universoWidth - 62) + 31; 611 | var y = Math.random() * (universoHeight - 62) + 31; 612 | var raio = Math.random() * 1.5 + 1; 613 | 614 | if(Alimento.alimentos.length < 3000){ // Limitador para não sobrecarregar a simulação 615 | new Alimento(x, y, raio); 616 | } 617 | } 618 | } else{ 619 | var x = Math.random() * (universoWidth - 62) + 31; 620 | var y = Math.random() * (universoHeight - 62) + 31; 621 | var raio = Math.random() * 1.5 + 1; 622 | 623 | if(Alimento.alimentos.length < 3000){ // Limitador para não sobrecarregar a simulação 624 | new Alimento(x, y, raio); 625 | } 626 | } 627 | } 628 | } 629 | 630 | function mudaIntervaloAlimentos(novoValor, criar=false) { 631 | novoTempo = 1000 / novoValor 632 | if(!criar) { 633 | clearInterval(intervaloTaxaAlimentos); 634 | } 635 | if(novoTempo > 1000) return; 636 | if(antesDoPlay) return; 637 | intervaloTaxaAlimentos = setInterval(criaAlimentosGradativo, novoTempo) 638 | } 639 | 640 | function mudaProbMutacao(novoValor){ 641 | probabilidade_mutacao = novoValor / 100; 642 | } 643 | 644 | function mudaMagMutacao(novoValor){ 645 | magnitude_mutacao = novoValor / 100; 646 | } 647 | 648 | function desenhaDivisao(){ 649 | c.beginPath(); 650 | c.moveTo(universoWidth / 2, 0); 651 | c.lineTo(universoWidth / 2, universoHeight); 652 | c.strokeStyle = "white"; 653 | c.stroke(); 654 | } 655 | 656 | function desenhaQuadTree(qtree){ 657 | qtree.desenha(); 658 | 659 | let alcance = new Retangulo(Math.random() * universoWidth, Math.random() * universoHeight, 170, 123); 660 | c.rect(alcance.x - alcance.w, alcance.y - alcance.h, alcance.w*2, alcance.h*2); 661 | c.strokeStyle = "green"; 662 | c.lineWidth = 3; 663 | c.stroke(); 664 | 665 | let pontos = qtree.procura(alcance); 666 | for(let p of pontos){ 667 | c.beginPath(); 668 | c.arc(p.x, p.y, 1, 0, 2 * Math.PI); 669 | c.strokeStyle = "red"; 670 | c.stroke(); 671 | } 672 | } 673 | 674 | function criaPontos(){ 675 | let congregacao = new Ponto(Math.random() * universoWidth, Math.random() * universoHeight); 676 | 677 | for(var i = 0; i < 500; i++){ 678 | let p = new Ponto(Math.random() * universoWidth, Math.random() * universoHeight); 679 | qtree.inserirPonto(p); 680 | } 681 | for(var i = 0; i < 300; i++){ 682 | let p = new Ponto(congregacao.x + (Math.random() - 0.5) * 300, congregacao.y + (Math.random() - 0.5) * 300); 683 | qtree.inserirPonto(p); 684 | } 685 | for(var i = 0; i < 400; i++){ 686 | let p = new Ponto(congregacao.x + (Math.random() - 0.5) * 600, congregacao.y + (Math.random() - 0.5) * 600); 687 | qtree.inserirPonto(p); 688 | } 689 | for(var i = 0; i < 400; i++){ 690 | let p = new Ponto(congregacao.x + (Math.random() - 0.5) * 800, congregacao.y + (Math.random() - 0.5) * 800); 691 | qtree.inserirPonto(p); 692 | } 693 | } 694 | 695 | function calculaDadosGrafico(){ 696 | // Liberar espaço de memória das variáveis anteriores 697 | popH = velMedH = forcaMedH = raioMedH = raioDetMedH = energMedH = taxaEnergMedH = ninhadaMediaH = null; 698 | popC = velMedC = forcaMedC = raioMedC = raioDetMedC = energMedC = taxaEnergMedC = ninhadaMediaC = null; 699 | 700 | // Resetando as variáveis para os herbívoros 701 | popH = {sem_div: 0, esq: 0, dir: 0} 702 | velMedH = {sem_div: 0, esq: 0, dir: 0}; 703 | forcaMedH = {sem_div: 0, esq: 0, dir: 0}; 704 | raioMedH = {sem_div: 0, esq: 0, dir: 0}; 705 | raioDetMedH = {sem_div: 0, esq: 0, dir: 0}; 706 | energMedH = {sem_div: 0, esq: 0, dir: 0}; 707 | taxaEnergMedH = {sem_div: 0, esq: 0, dir: 0}; 708 | ninhadaMediaH = {sem_div: 0, esq: 0, dir: 0}; 709 | 710 | // Resetando as variáveis para os carnívoros 711 | popC = {sem_div: 0, esq: 0, dir: 0} 712 | velMedC = {sem_div: 0, esq: 0, dir: 0}; 713 | forcaMedC = {sem_div: 0, esq: 0, dir: 0}; 714 | raioMedC = {sem_div: 0, esq: 0, dir: 0}; 715 | raioDetMedC = {sem_div: 0, esq: 0, dir: 0}; 716 | energMedC = {sem_div: 0, esq: 0, dir: 0}; 717 | taxaEnergMedC = {sem_div: 0, esq: 0, dir: 0}; 718 | ninhadaMediaC = {sem_div: 0, esq: 0, dir: 0}; 719 | 720 | 721 | Herbivoro.herbivoros.forEach(herbivoro => { 722 | // Soma o valor das variáveis pra todos os herbívoros 723 | popH["sem_div"]++ 724 | velMedH["sem_div"] += herbivoro.vel_max; 725 | forcaMedH["sem_div"] += herbivoro.forca_max; 726 | raioMedH["sem_div"] += herbivoro.raio_inicial * 1.5; // o raio máximo é (1.5 * raio_inicial) 727 | raioDetMedH["sem_div"] += herbivoro.raio_deteccao_inicial * 1.3; // 1.3 e não 1.5 pois o raio de detecção aumenta menos que o raio 728 | energMedH["sem_div"] += herbivoro.energia_max_fixa; 729 | taxaEnergMedH["sem_div"] += herbivoro.taxa_gasto_energia_max; 730 | ninhadaMediaH["sem_div"] += (herbivoro.intervalo_ninhada[0] + herbivoro.intervalo_ninhada[1]) / 2; 731 | 732 | if(telaDividida){ 733 | // Checa se está a direita ou a esquerda 734 | let lado; 735 | if(herbivoro.posicao.x < universoWidth / 2) { 736 | lado = "esq" 737 | } else { 738 | lado = "dir" 739 | } 740 | // Soma o valor das variáveis pra todos os herbívoros 741 | popH[lado]++ 742 | velMedH[lado] += herbivoro.vel_max; 743 | forcaMedH[lado] += herbivoro.forca_max; 744 | raioMedH[lado] += herbivoro.raio_inicial * 1.5; // o raio máximo é (1.5 * raio_inicialimo) 745 | raioDetMedH[lado] += herbivoro.raio_deteccao_inicial * 1.3; // 1.3 e não 1.5 pois o raio de detecção aumenta menos que o raio 746 | energMedH[lado] += herbivoro.energia_max_fixa; 747 | taxaEnergMedH[lado] += herbivoro.taxa_gasto_energia_max; 748 | ninhadaMediaH[lado] += (herbivoro.intervalo_ninhada[0] + herbivoro.intervalo_ninhada[1]) / 2; 749 | } 750 | }); 751 | 752 | Carnivoro.carnivoros.forEach(carnivoro => { 753 | // Soma o valor das variáveis pra todos os carnívoros 754 | popC["sem_div"]++ 755 | velMedC["sem_div"] += carnivoro.vel_max; 756 | forcaMedC["sem_div"] += carnivoro.forca_max; 757 | raioMedC["sem_div"] += carnivoro.raio_inicial * 1.5; // o raio máximo é (1.5 * raio_inicial) 758 | raioDetMedC["sem_div"] += carnivoro.raio_deteccao_inicial * 1.3; // 1.3 e não 1.5 pois o raio de detecção aumenta menos que o raio 759 | energMedC["sem_div"] += carnivoro.energia_max_fixa; 760 | taxaEnergMedC["sem_div"] += carnivoro.taxa_gasto_energia_max; 761 | ninhadaMediaC["sem_div"] += (carnivoro.intervalo_ninhada[0] + carnivoro.intervalo_ninhada[1]) / 2; 762 | 763 | if(telaDividida){ 764 | // Checa se está a direita ou a esquerda 765 | let lado; 766 | if(carnivoro.posicao.x < universoWidth / 2) { 767 | lado = "esq" 768 | } else { 769 | lado = "dir" 770 | } 771 | // Soma o valor das variáveis pra todos os carnívoros 772 | popC[lado]++ 773 | velMedC[lado] += carnivoro.vel_max; 774 | forcaMedC[lado] += carnivoro.forca_max; 775 | raioMedC[lado] += carnivoro.raio_inicial * 1.5; // o raio máximo é (1.5 * raio_inicialimo) 776 | raioDetMedC[lado] += carnivoro.raio_deteccao_inicial * 1.3; // 1.3 e não 1.5 pois o raio de detecção aumenta menos que o raio 777 | energMedC[lado] += carnivoro.energia_max_fixa; 778 | taxaEnergMedC[lado] += carnivoro.taxa_gasto_energia_max; 779 | ninhadaMediaC[lado] += (carnivoro.intervalo_ninhada[0] + carnivoro.intervalo_ninhada[1]) / 2; 780 | } 781 | }); 782 | 783 | 784 | // Divide o valor (a soma total) pelo número de herbívoros para obter a média 785 | // Sem divisão 786 | velMedH.sem_div /= popH.sem_div; 787 | forcaMedH.sem_div /= popH.sem_div; 788 | raioMedH.sem_div /= popH.sem_div; 789 | raioDetMedH.sem_div /= popH.sem_div; 790 | energMedH.sem_div /= popH.sem_div; 791 | taxaEnergMedH.sem_div /= popH.sem_div; 792 | ninhadaMediaH.sem_div /= popH.sem_div; 793 | // Lado esquerdo 794 | velMedH.esq /= popH.esq; 795 | forcaMedH.esq /= popH.esq; 796 | raioMedH.esq /= popH.esq; 797 | raioDetMedH.esq /= popH.esq; 798 | energMedH.esq /= popH.esq; 799 | taxaEnergMedH.esq /= popH.esq; 800 | ninhadaMediaH.esq /= popH.esq; 801 | // Lado direito 802 | velMedH.dir /= popH.dir; 803 | forcaMedH.dir /= popH.dir; 804 | raioMedH.dir /= popH.dir; 805 | raioDetMedH.dir /= popH.dir; 806 | energMedH.dir /= popH.dir; 807 | taxaEnergMedH.dir /= popH.dir; 808 | ninhadaMediaH.dir /= popH.dir; 809 | 810 | // Divide o valor (a soma total) pelo número de carnívoros para obter a média 811 | // Sem divisão 812 | velMedC.sem_div /= popC.sem_div; 813 | forcaMedC.sem_div /= popC.sem_div; 814 | raioMedC.sem_div /= popC.sem_div; 815 | raioDetMedC.sem_div /= popC.sem_div; 816 | energMedC.sem_div /= popC.sem_div; 817 | taxaEnergMedC.sem_div /= popC.sem_div; 818 | ninhadaMediaC.sem_div /= popC.sem_div; 819 | // Lado esquerdo 820 | velMedC.esq /= popC.esq; 821 | forcaMedC.esq /= popC.esq; 822 | raioMedC.esq /= popC.esq; 823 | raioDetMedC.esq /= popC.esq; 824 | energMedC.esq /= popC.esq; 825 | taxaEnergMedC.esq /= popC.esq; 826 | ninhadaMediaC.esq /= popC.esq; 827 | // Lado direito 828 | velMedC.dir /= popC.dir; 829 | forcaMedC.dir /= popC.dir; 830 | raioMedC.dir /= popC.dir; 831 | raioDetMedC.dir /= popC.dir; 832 | energMedC.dir /= popC.dir; 833 | taxaEnergMedC.dir /= popC.dir; 834 | ninhadaMediaC.dir /= popC.dir; 835 | } 836 | 837 | function checaPopulacoesDivididas(){ 838 | if(telaDividida){ 839 | lado_direito_vazio = true; 840 | lado_esquerdo_vazio = true; 841 | 842 | Herbivoro.herbivoros.forEach(herbivoro => { 843 | // Checa lado esquerdo 844 | if(herbivoro.posicao.x < universoWidth / 2 - 31){ 845 | lado_esquerdo_vazio = false; 846 | } 847 | 848 | // Checa lado direito 849 | if(herbivoro.posicao.x > universoWidth / 2 + 31){ 850 | lado_direito_vazio = false; 851 | } 852 | }) 853 | } 854 | } 855 | 856 | var idAnimate; 857 | 858 | function pausa(){ 859 | pausado = true; 860 | 861 | btnPausa.classList.add("d-none"); 862 | btnDespausa.classList.remove("d-none"); 863 | 864 | } 865 | 866 | function despausa(){ 867 | pausado = false; 868 | 869 | btnDespausa.classList.add("d-none"); 870 | btnPausa.classList.remove("d-none"); 871 | 872 | animate(); 873 | } 874 | 875 | function acelera(){ 876 | animate(); 877 | 878 | // btnDesacelera.classList.remove("d-none"); 879 | } 880 | 881 | function desacelera(){ 882 | pausa(); 883 | setTimeout(despausa, 10); 884 | } 885 | 886 | function animate(){ 887 | if(pausado == false){ 888 | idAnimate = requestAnimationFrame(animate); 889 | } 890 | 891 | c.clearRect(0, 0, universoWidth, universoHeight); 892 | c.beginPath(); 893 | c.moveTo(-3, -4); 894 | c.lineTo(universoWidth + 3, -3); 895 | c.lineTo(universoWidth + 3, universoHeight + 3); 896 | c.lineTo(-3, universoHeight + 3); 897 | c.lineTo(-3, -3); 898 | c.strokeStyle = "white"; 899 | c.stroke(); 900 | 901 | // Criando a Quadtree 902 | let qtree = new QuadTree(retanguloCanvas, 10); 903 | 904 | // Divisão de tela 905 | if(checkbox_divisao.checked){ 906 | telaDividida = true; 907 | } else{ 908 | telaDividida = false; 909 | } 910 | 911 | if(telaDividida){ 912 | desenhaDivisao(); 913 | 914 | Alimento.alimentos.forEach((alimento, i) => { 915 | alimento.display(); 916 | // remove alimentos próximos da divisão para evitar que organismos se atraiam para perto dela 917 | if(alimento.posicao.x - universoWidth / 2 < 30 && alimento.posicao.x - universoWidth / 2 > -30){ 918 | Alimento.alimentos.splice(i, 1); 919 | } 920 | 921 | qtree.inserirAlimento(alimento); // Insere o alimento na QuadTree 922 | 923 | }) 924 | 925 | if(limitador_de_loop < 10){ 926 | limitador_de_loop++; 927 | } 928 | 929 | Organismo.organismos.forEach((organismo) => { 930 | if(organismo.posicao.x <= universoWidth/2){ // se o organismo estiver na parte esquerda 931 | if(limitador_de_loop == 1 && universoWidth/2 - organismo.posicao.x < 10){ // empurra os organismos pertos da borda para o lado 932 | organismo.posicao.x -= 10; 933 | } 934 | organismo.criaBordas(true); // telaDividida: true 935 | } else{ // se o organismo estiver na parte direita 936 | if(limitador_de_loop == 1 && organismo.posicao.x - universoWidth/2 < 10){ // empurra os organismos pertos da borda para o lado 937 | organismo.posicao.x += 10; 938 | } 939 | organismo.criaBordas(true); // telaDividida: true 940 | } 941 | }) 942 | 943 | // Inserindo os organismos na QuadTree antes de chamar os métodos de cada um 944 | Herbivoro.herbivoros.forEach(herbivoro => { 945 | qtree.inserirHerbivoro(herbivoro); // Insere o herbivoro na QuadTree 946 | }); 947 | Carnivoro.carnivoros.forEach(carnivoro => { 948 | qtree.inserirCarnivoro(carnivoro); // Insere o carnivoro na QuadTree 949 | }); 950 | 951 | // Chamando os métodos dos organismos 952 | Herbivoro.herbivoros.forEach(herbivoro => { 953 | herbivoro.update(); 954 | herbivoro.vagueia(); 955 | 956 | // Transforma o raio de detecção em um objeto círculo para podermos manipulá-lo 957 | let visaoH = new Circulo(herbivoro.posicao.x, herbivoro.posicao.y, herbivoro.raio_deteccao); 958 | 959 | // herbivoro.buscarAlimento(qtree, visaoH); 960 | if(herbivoro.energia <= herbivoro.energia_max * fome_h){ // FOME 961 | herbivoro.buscarAlimento(qtree, visaoH); 962 | } 963 | herbivoro.detectaPredador(qtree, visaoH); 964 | }) 965 | 966 | Carnivoro.carnivoros.forEach(carnivoro => { 967 | carnivoro.update(); 968 | carnivoro.vagueia(); 969 | 970 | // Transforma o raio de detecção em um objeto círculo para podermos manipulá-lo 971 | let visaoC = new Circulo(carnivoro.posicao.x, carnivoro.posicao.y, carnivoro.raio_deteccao); 972 | 973 | // carnivoro.buscarHerbivoro(qtree, visaoC); 974 | if(carnivoro.energia <= carnivoro.energia_max * fome_c){ // FOME 975 | carnivoro.buscarHerbivoro(qtree, visaoC); 976 | } 977 | }) 978 | } else{ // se a tela NÃO estiver dividida 979 | limitador_de_loop = 0; 980 | 981 | Alimento.alimentos.forEach(alimento => { 982 | alimento.display(); 983 | qtree.inserirAlimento(alimento); // Insere o alimento na QuadTree 984 | 985 | }) 986 | 987 | Organismo.organismos.forEach((organismo) => { 988 | organismo.criaBordas(false); // telaDividida: false 989 | }) 990 | 991 | // Inserindo os organismos na QuadTree antes de chamar os métodos de cada um 992 | Herbivoro.herbivoros.forEach(herbivoro => { 993 | qtree.inserirHerbivoro(herbivoro); // Insere o herbivoro na QuadTree 994 | }); 995 | Carnivoro.carnivoros.forEach(carnivoro => { 996 | qtree.inserirCarnivoro(carnivoro); // Insere o carnivoro na QuadTree 997 | }); 998 | 999 | // Chamando os métodos dos organismos 1000 | Herbivoro.herbivoros.forEach(herbivoro => { 1001 | herbivoro.update(); 1002 | herbivoro.vagueia(); 1003 | 1004 | // Transforma o raio de detecção em um objeto círculo para podermos manipulá-lo 1005 | let visaoH = new Circulo(herbivoro.posicao.x, herbivoro.posicao.y, herbivoro.raio_deteccao); 1006 | 1007 | // herbivoro.buscarAlimento(qtree, visaoH); 1008 | if(herbivoro.energia <= herbivoro.energia_max * fome_h){ // FOME 1009 | herbivoro.buscarAlimento(qtree, visaoH); 1010 | } 1011 | 1012 | herbivoro.detectaPredador(qtree, visaoH); 1013 | }) 1014 | 1015 | Carnivoro.carnivoros.forEach(carnivoro => { 1016 | carnivoro.update(); 1017 | carnivoro.vagueia(); 1018 | 1019 | // Transforma o raio de detecção em um objeto círculo para podermos manipulá-lo 1020 | let visaoC = new Circulo(carnivoro.posicao.x, carnivoro.posicao.y, carnivoro.raio_deteccao); 1021 | 1022 | // carnivoro.buscarHerbivoro(qtree, visaoC); 1023 | if(carnivoro.energia <= carnivoro.energia_max * fome_c){ // FOME 1024 | carnivoro.buscarHerbivoro(qtree, visaoC); 1025 | } 1026 | }) 1027 | } 1028 | } 1029 | 1030 | function geraInteiro(min, max) { 1031 | min = Math.ceil(min); 1032 | max = Math.floor(max); 1033 | return Math.floor(Math.random() * (max - min)) + min; 1034 | } 1035 | // ---------------------------------------------------------------------------------------------- 1036 | // Paineis dinamicos e Popovers 1037 | // ---------------------------------------------------------------------------------------------- 1038 | // Função atrelada ao evento click para encontrar o organismo na lista e retornar suas propriedades 1039 | function getOrganismo(x, y) { 1040 | let organismo = Organismo.organismos.find(o => Math.abs(o.posicao.x - x) <= 10 && Math.abs(o.posicao.y - y) <= 10) 1041 | if(organismo == undefined) { 1042 | return; //console.log("não encontrou") 1043 | } 1044 | 1045 | // Verificar se ja existe um popover referente ao organismo clicado 1046 | let popoverJaExiste = document.querySelectorAll(`.popover-info[data-organismoid="${organismo.id}"]`).length > 0 ? 1:0 1047 | if (popoverJaExiste) { 1048 | return; 1049 | } 1050 | 1051 | let popover = ` 1052 |
1053 |
1054 | ${(organismo instanceof Carnivoro) ? "Carnívoro":"Herbívoro"}
#${organismo.id}
1055 |
1056 |
1057 | Sexo:
${organismo.sexo}

1058 | Raio:
${organismo.raio.toFixed(2)}
/${(organismo.raio_inicial * 1.5).toFixed(2)}
1059 | Velocidade:
${organismo.vel.mag().toFixed(2)}
/${organismo.vel_max.toFixed(2)}
1060 | Raio de detecção:
${organismo.raio_deteccao.toFixed(2)}

1061 | Energia:
${organismo.energia.toFixed(2)}
/
${organismo.energia_max.toFixed(2)}

1062 | Gasto energético:
${(organismo.taxa_gasto_energia + organismo.gasto_minimo).toFixed(3)}

1063 | Cor: ${organismo.cor}
1064 | 1065 | Status:
${organismo.status}

1066 | Idade:
${organismo.tempo_vivido}
/${organismo.tempo_vida}
1067 | Ninhada: de
${organismo.intervalo_ninhada[0]}
a
${organismo.intervalo_ninhada[1]}

1068 | Filhos:
${organismo.filhos.length}

1069 | 1070 |
1071 | 1075 |
1076 | ` 1077 | $("body").append($(popover)); 1078 | 1079 | let pop_id = popover_id 1080 | // CRIAR MONITORADOR PARA A VARIÁVEL POSICAO 1081 | organismo.proxy = new Proxy(organismo["posicao"], { 1082 | set: function(target_org, key_position, value) { 1083 | target_org[key_position] = value; 1084 | 1085 | //console.log("vetor mudou: "+key_position+" = "+value) 1086 | 1087 | let cssProperty = key_position == "x" ? 1088 | 1089 | {left: parseInt((value + 15 - c.transformedPoint(0, 0).x))} : 1090 | {top: parseInt(value - 20 - c.transformedPoint(0, 0).y)} 1091 | // Popover acompanhar a posicao do organismo 1092 | $(`#popover-${pop_id}`).css(cssProperty); 1093 | document.getElementById(`pop-raio-${pop_id}`).textContent = organismo.raio.toFixed(1); 1094 | document.getElementById(`pop-vel-${pop_id}`).textContent = organismo.vel.mag().toFixed(2); 1095 | document.getElementById(`pop-deteccao-${pop_id}`).textContent = organismo.raio_deteccao.toFixed(2); 1096 | document.getElementById(`pop-energia-${pop_id}`).textContent = organismo.energia.toFixed(1); 1097 | document.getElementById(`pop-energia-max-${pop_id}`).textContent = organismo.energia_max.toFixed(1); 1098 | document.getElementById(`pop-gasto-${pop_id}`).textContent = (organismo.taxa_gasto_energia + organismo.gasto_minimo).toFixed(3); 1099 | document.getElementById(`pop-status-${pop_id}`).textContent = organismo.status; 1100 | document.getElementById(`pop-vezes-reproduzidas-${pop_id}`).textContent = organismo.vezes_reproduzidas; 1101 | // organismo.energia <= organismo.energia_max * 0.8 ? document.getElementById(`pop-fome-${pop_id}`).textContent = "Com fome": document.getElementById(`pop-fome-${pop_id}`).textContent = "Satisfeito" 1102 | document.getElementById(`pop-vida-${pop_id}`).textContent = organismo.tempo_vivido; 1103 | return true; 1104 | } 1105 | }) 1106 | 1107 | // SALVAR O ID DO POPOVER NO ORGANISMO 1108 | organismo.popover_id = pop_id 1109 | 1110 | popover_id++ 1111 | } 1112 | 1113 | function deletePopover(popoverId, organismoId) { 1114 | // Capturar organismo 1115 | const organismo = Organismo.organismos.find(o => o.id == organismoId) || 0; 1116 | if(organismo) { 1117 | delete organismo.proxy 1118 | delete organismo.popover_id 1119 | } 1120 | $(`#popover-${popoverId}`).remove() 1121 | 1122 | // Esconder botao de fechar todos os popovers se nao existem mais popover abertos 1123 | if($(".popover-info").length == 0) { 1124 | $("#btnDeletePopovers").hide(); 1125 | } 1126 | } 1127 | 1128 | function excluirOrganismoPopover(popoverId, organismoId){ 1129 | // Capturar organismo 1130 | const organismo = Organismo.organismos.find(o => o.id == organismoId) || 0; 1131 | if(organismo) { 1132 | organismo.morre(); 1133 | if(pausado){ 1134 | despausa(); // Se não fizer isso, o organismo continua aparecendo enquanto estiver pausado 1135 | pausa(); 1136 | } 1137 | } 1138 | $(`#popover-${popoverId}`).remove() 1139 | } 1140 | 1141 | // GERAR PAINEL DE ESCOLHA DAS PROPRIEDADES DOS ORGANISMOS ADICIONADOS NA TELA 1142 | function showEditPanel(type) { 1143 | // Restaurar configuracoes salvas 1144 | let config; 1145 | if(type == 1) { 1146 | config = conf_c; 1147 | } else { 1148 | config = conf_h; 1149 | } 1150 | 1151 | let panel = ` 1152 | 1153 |
1154 |
${type == 1? "Carnívoro":"Herbívoro"}
1155 | 1156 | 1157 | 1160 |
1161 | 1162 | 1163 |
1164 |
1165 |
1166 | 1167 | 1168 | 1169 |
1170 |
1171 |
1172 |
1173 | 1174 | 1175 |
1176 |
1177 | 1178 | 1179 |
1180 |
1181 | 1182 | 1183 |
1184 |
1185 | 1186 | 1187 |
1188 |
1189 | 1190 | 1191 | 1192 |
1193 |
1194 |
1195 |
1196 | 1197 |
1198 | 1199 | 1200 | ` 1201 | $("#painelEditar").html(panel).removeClass("d-none") 1202 | // Iniciar como aleatorio se não existe configuracao previa salva 1203 | if(!config) { 1204 | randomConfig(type); 1205 | } 1206 | } 1207 | 1208 | function serializarFormConfig(type) { 1209 | let obj = $("#formConfig").serializeArray().reduce(function(obj, value, i) { 1210 | obj[value.name] = value.value; 1211 | return obj; 1212 | }, {}); 1213 | // Converter cor 1214 | obj.cor = hexToRgb(obj["cor"]) 1215 | 1216 | // Converter numeros 1217 | obj.raio_inicial = parseFloat(obj.raio_inicial); 1218 | obj.vel_max = parseFloat(obj.vel_max); 1219 | obj.forca_max = parseFloat(obj.forca_max); 1220 | obj.raio_deteccao_inicial = parseFloat(obj.raio_deteccao_inicial); 1221 | obj.intervalo_ninhada = obj.intervalo_ninhada; 1222 | 1223 | if(type == 1) { 1224 | conf_c = obj; 1225 | } else { 1226 | conf_h = obj; 1227 | } 1228 | } 1229 | 1230 | function randomConfig(type) { 1231 | if($("#edit-random").hasClass("active")) { 1232 | $("#edit-random").removeClass("active"); 1233 | 1234 | // Retirar disable dos inputs 1235 | $("#formConfig input").prop("disabled", false) 1236 | } else { 1237 | // apagar configuracao 1238 | if(type==1 && conf_c) { 1239 | // TODO: aviso para confirmar se quer aleatorizar mesmo. 1240 | let resultado = confirm("Ao aleatorizar os valores, você perderá as configurações salvas para os Carnívoros. Deseja continuar?") 1241 | if(resultado == true) 1242 | conf_c = undefined; 1243 | else 1244 | return; 1245 | } else if(type==2 && conf_h) { 1246 | // TODO: aviso para confirmar se quer aleatorizar mesmo. 1247 | let resultado = confirm("Ao aleatorizar os valores, você perderá as configurações salvas para os Herbívoros. Deseja continuar?") 1248 | if(resultado == true) 1249 | conf_h = undefined; 1250 | else 1251 | return; 1252 | } 1253 | $("#edit-random").addClass("active"); 1254 | // dar disable nos inputs de configuracao 1255 | $("#formConfig input").prop("disabled", true) 1256 | } 1257 | } 1258 | 1259 | // ---------------------------------------------------------------------------------------------- 1260 | // Cronômetro 1261 | // ---------------------------------------------------------------------------------------------- 1262 | var cronometro; 1263 | 1264 | function criaCronometro(){ 1265 | cronometro = setInterval(() => { timer(); }, 10); 1266 | } 1267 | 1268 | function timer() { 1269 | if(!pausado){ // Só atualiza se a simulação não estiver pausada 1270 | if ((milisegundo += 10) == 1000) { 1271 | milisegundo = 0; 1272 | segundo++; 1273 | segundos_totais++; 1274 | } 1275 | if (segundo == 60) { 1276 | segundo = 0; 1277 | minuto++; 1278 | } 1279 | if (minuto == 60) { 1280 | minuto = 0; 1281 | hora++; 1282 | } 1283 | document.getElementById('hora').innerText = returnData(hora); 1284 | document.getElementById('minuto').innerText = returnData(minuto); 1285 | document.getElementById('segundo').innerText = returnData(segundo); 1286 | document.getElementById('milisegundo').innerText = returnData(milisegundo); 1287 | } 1288 | } 1289 | 1290 | function returnData(input) { 1291 | return input > 10 ? input : `0${input}` 1292 | } 1293 | 1294 | function resetaCronometro(){ 1295 | hora = minuto = segundo = milisegundo = segundos_totais = 0; 1296 | 1297 | //limpar o cronometro se ele existe. 1298 | try { 1299 | clearInterval(cronometro); 1300 | } catch(e){} 1301 | 1302 | document.getElementById('hora').innerText = "00"; 1303 | document.getElementById('minuto').innerText = "00"; 1304 | document.getElementById('segundo').innerText = "00"; 1305 | document.getElementById('milisegundo').innerText = "00"; 1306 | } 1307 | 1308 | function makeId(length) { 1309 | var result = ''; 1310 | var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 1311 | var charactersLength = characters.length; 1312 | for ( var i = 0; i < length; i++ ) { 1313 | result += characters.charAt(Math.floor(Math.random() * 1314 | charactersLength)); 1315 | } 1316 | return result; 1317 | } 1318 | 1319 | // ---------------------------------------------------------------------------------------------- 1320 | // Frame rate 1321 | // ---------------------------------------------------------------------------------------------- 1322 | 1323 | // // The higher this value, the less the fps will reflect temporary variations 1324 | // // A value of 1 will only keep the last value 1325 | // var filterStrength = 20; 1326 | // var frameTime = 0, lastLoop = new Date, thisLoop; 1327 | 1328 | // function gameLoop(){ 1329 | // // ... 1330 | // var thisFrameTime = (thisLoop=new Date) - lastLoop; 1331 | // frameTime+= (thisFrameTime - frameTime) / filterStrength; 1332 | // lastLoop = thisLoop; 1333 | // } 1334 | 1335 | // // Report the fps only every second, to only lightly affect measurements 1336 | // var fpsOut = document.getElementById('framerate'); 1337 | // setInterval(function(){ 1338 | // fpsOut.innerHTML = parseFloat((1000/frameTime).toFixed(1)) + " fps"; 1339 | // },500); 1340 | 1341 | // // function calculaFrameRate(){ 1342 | // // var fps; 1343 | // // var thisLoop = new Date(); 1344 | // // fps = 1000/(thisLoop - lastLoop); 1345 | // // lastLoop = thisLoop; 1346 | 1347 | // // return fps; 1348 | // // document.getElementById("framerate").innerHTML = fps; 1349 | // // } 1350 | 1351 | // setInterval(() => { 1352 | // var thisLoop = new Date(); 1353 | // var fps = 1000/(thisLoop - lastLoop); 1354 | // lastLoop = thisLoop; 1355 | 1356 | // document.getElementById("framerate").innerHTML = fps; 1357 | // }, 1000); 1358 | --------------------------------------------------------------------------------