MoveSearcher{ public static ArrayList&ltTetrisMove&gt getValidMoves (TetrisBoard board, TetrisPiece piece, TetrisAgent agent) { ArrayList&ltTetrisMove&gt moves = new ArrayList&ltTetrisMove&gt(); Point originalPosition = new Point(piece.position.x, piece.position.y); for (int i = 0; i < 4; i++) { piece.rotation = i; int startY = piece.position.y; while (board.canMoveLeft(piece)) piece.moveLeft(); do { while(board.canMoveDown(piece)) piece.moveDown(); board.updateBoard(piece); float eval = agent.eval(board.board); TetrisMove tempMove = new TetrisMove(); tempMove.eval = eval; tempMove.position.x = piece.position.x; tempMove.position.y = piece.position.y; tempMove.rotation = piece.rotation; moves.add(tempMove); board.removePiece(piece); piece.position.y = startY; if (! board.canMoveRight(piece)) { break; } else { piece.moveRight(); } } while(true); piece.position = originalPosition; } return moves; } public static TetrisMove getBestMove (TetrisBoard board, TetrisPiece piece, TetrisAgent agent) { TetrisMove best = null; ArrayList&ltTetrisMove&gt moves = getValidMoves(board, piece, agent); for(TetrisMove move : moves) { if (best == null) best = move; else if (move.eval > best.eval) best = move; } return best; } } //fim da classe MoveSearcherClasse AIGame Essa classe define o game loop da AI, nos permitindo visualizar o agente jogando. Como comentado anteriormente, durante a execucao da GA, cada agente sera compartilhado por n Threads diferentes, onde cada Thread representa um game com uma sequencia fixa de pecas. Veja a figura abaixo: No post anterior, implementamos a classe GamePanel, que permite a execucao de um game em uma Thread separada, aceitando input do teclado. Nest post, AIGame ira herdar de GamePanel e sobrescrever o metodo run. Olhando um pouco adiante, sabemos que a qualidade de cada agente sera quantificada tomando-se a media do numero de linhas removidas em n games. Ou seja, para calcular esse valor, devemos nos certificar de que todas as Threads deste agente tenham finalizado a execucao. A classe CountDownLatch, do pacote java.util.concurrent, nos permite fazer essa sincronia facilmente. Como estamos testando apenas um agente, nao ha necessidade de sincronia, e a variavel doneSignal sera null por enquanto. A variavel cleared representa o numero de linhas removidas ao fim do jogo. O override do metodo run e simples: Consultamos MoveSearcher para obter o melhor lance, passando como argumentos a grade, uma copia da peca e o agente. Atualizamos o estado do game de maneira similar ao metodo da superclasse, mas aqui incrementamos a variavel cleared a cada linha removida. Fazemos a Thread dormir por um pequeno intervalo de tempo, o que permite uma renderizacao mais suave. Em caso de game over, atualizamos a variavel clearedRows do agente e invocamos doneSignal.countDown, sinalizando que esta Thread finalizou seu trabalho. Esse mecanismo ficara claro no proximo post, mas por enquanto nao faz diferenca, afinal doneSignal e null por enquanto. Segue a classe AIGame:import java.util.Random;import javax.swing.*;import java.awt.*;import java.util.concurrent.*;import java.io.*;public class AIGame extends GamePanel{ private CountDownLatch doneSignal; private boolean doSleep = false; TetrisAgent agent = new TetrisAgent(); int cleared = 0; public AIGame(int r, int c, int bs, int seed, CountDownLatch signal) { this(r, c, bs, seed, false); this.doneSignal = signal; } public AIGame(int r, int c, int bs, int seed, boolean doSleep) { super(r, c, bs, seed); this.doSleep = doSleep; } public AIGame(int r, int c, int bs, boolean doSleep) { super(r, c, bs); this.doSleep = doSleep; } public void run() { fallingPiece = randomPiece();