na GA. O caso continuo novamente exige uma tecnica especial. Para manter as coisas simples, o metodo mutateNoise apenas percorre os genes, e introduz ou nao, uma pequena variacao noise. A probabilidade de um gene sofrer mutacao, probMutate, e um dos parametros mais importantes da GA, e deve ser ajustado para cada problema.Selecao Como comentado anteriormente, agentes com melhor desempenho terao mais chances de serem selecionados para reproducao e passar seus genes adiante. O metodo doSelection usa roulette wheel selection para selecionar e retornar um agente, lembrando que os valores de fitness devem ser nao-negativos ao utilizar esta tecnica.Classe GeneticAlgorithm Esta e a classe que implementa a GA e os metodos discutidos acima. O metodo runGA executa o algoritmo, e retorna a ArrayList hist, onde cada item representa uma geracao de agentes. Os parametros popSize, maxGens e probMutate representam respectivamente o numero de agentes em cada geracao, o numero maximo de geracoes e a probabilidade de ocorrer mutacao em um gene. Os parametros gamesPerAgent e eliteRate representam respectivamente o numero de jogos por agente, e a taxa de elitismo utilizada. Repare que no comeco do metodo forcamos popSize e elite a assumirem valores par, isso evitara problemas caso haja elitismo, pois garantimos que o numero de agentes necessarios para completar a populacao, (popSize - elite)/2, tambem seja par. O loop principal do metodo parece complicado, mas e bem simples. Comecamos sorteando um inteiro que servira como ponto de partida para semear o gerador aleatorio. Em seguida, para cada agente, iniciamos um game passando como argumento o numero de linhas e colunas, o tamanho do bloco, o valor para semear o gerador, e a variavel doneSignal. Como nao temos uma GUI associada, o tamanho do bloco nao importa, desde que seja diferente de zero. A variavel doneSignal representa um objeto da classe CountDownLatch, inicializado com o numero de Threads que desejamos esperar terminarem. Caso nao lembre, veja o final do metodo run da classe AIGame: invocamos doneSignal.countDown(), sinalizando que esta Thread terminou a execucao. Agora, ao invocar doneSignal.await(), podemos ter certeza que o programa so continuara a execucao apos todos os games/threads terem finalizado. Em seguida, calculamos a media de linhas removidas por cada agente e atualizamos fitness e bestAgent. Seguindo com o codigo, alocamos a array que vai acomodar a nova populacao e invocamos sort na populacao atual. Agora os agentes estao ordenados de acordo com o desempenho quantificado em fitness (lembre-se que a classe TetrisAgent implementa a interface Comparable), e prosseguimos selecionando a elite, que automaticamente fara parte da nova populacao. Por fim, o restante da nova populacao e preenchido: selecionamos individuos da populacao aos pares, efetuamos o crossover e mutacao, e adicionamos os filhos a nova populacao. Ao final do loop adicionamos a populacao ao historico, fazemos a troca population = newPopulation e incrementamos a geracao atual. Por curiosidade, logamos o melhor agente obtido ate entao, ja que nao temos uma GUI. Repare que adotamos como criterio de parado o numero maximo de geracoes. Agora so nos falta setar os parametros de forma razoavel e executar o algoritmo. Para avaliar