< cols + 1) && ok; x++) if(board[y][x] == 0) ok = false; if(ok) { completeRows++; deleteRow(y); } } return completeRows; } public void deleteRow(int r) { for (int i = r; i > 0; i--) { for (int j = 1; j < cols + 1 ; j++) { board[i][j] = board[i - 1][j]; } } for (int j = 1; j < cols + 1 ; j++) { board[0][j] = 0; } } } //fim da classe TetrisBoardClasse GamePanel As classes TetrisPiece e TetrisBoard implementam objetos totalmente independentes, o que nos permite usar um esquema de composicao que seja mais vantajoso de acordo com a aplicacao. No nosso caso, cada agente ira evoluir com uma sequencia de n jogos diferentes, onde cada jogo sera executado em uma Thread propria. Essa parte e crucial, e sera explicada em detalhes mais adiante, o que nos interessa agora e definir GamePanel de forma que seja possivel executar varios games em paralelo, ou seja, implementando a interface Runnable. As variaveis board, fallingPiece e gameOver mantem o estado atual do game, e sao resetadas em startNewGame. O construtor base recebe o numero de linhas, colunas e o tamanho de cada bloco, inicializa algumas variaveis e habilita input do teclado. O segundo construtor nos permite semear o gerador aleatorio, ou seja, ao se passar o mesmo seed, a sequencia de pecas se repetira. Usaremos classes interessantes da api java.util.concurrent, que facilitam muito o trabalho com Threads. A classe ScheduledThreadPoolExecutor nos permite alocar um pool de Threads, que irao executar run a cada intervalo de tempo especificado. O metodo run implementa a logica do game: em caso de game over o metodo retorna e a Thread e interrompida, caso contrario, a peca continua a cair. Quando a peca repousa, board e atualizada, possiveis linhas completas sao verificadas, e addNewPiece e chamado. O metodo addNewPiece simplesmente checa as duas condicoes de game over, e atualiza fallingPiece. Ao final do loop, repaint ira invocar paintComponent, que por sua vez invoca renderGame, que e responsavel por desenhar a peca atual e o restante dos blocos. Segue a classe GamePanel: import javax.swing.*;import java.awt.*;import java.awt.event.*;import java.util.concurrent.ScheduledThreadPoolExecutor;import java.util.concurrent.TimeUnit;import java.util.Random;public class GamePanel extends JPanel implements Runnable{ TetrisBoard board; int blockSize; TetrisPiece fallingPiece; boolean gameOver; Random random; ScheduledThreadPoolExecutor animator; int cols; int rows; public GamePanel(int rows, int cols, int blockSize, int seed) { this(rows, cols, blockSize); random = new Random(seed); } public GamePanel(int rows, int cols, int blockSize) { this.blockSize = blockSize; this.setPreferredSize(new Dimension(cols * blockSize, rows * blockSize)); setDoubleBuffered(true); random = new Random(); board = new TetrisBoard(rows, cols); this.cols = cols; this.rows = rows; setFocusable(true); requestFocus(); addKeyWatcher(); } public void startNewGame() { gameOver = false; board.resetBoard(); fallingPiece = randomPiece(); long delayToStart = 100L; long step = 1000L/4; animator = null; animator = new ScheduledThreadPoolExecutor(1); animator.scheduleAtFixedRate(this, delayToStart, step, TimeUnit.MILLISECONDS); } public void run() { if (gameOver) { animator.shutdown(); return; } if (board.canMoveDown(fallingPiece)) { fallingPiece.moveDown(); } else { board.updateBoard(fallingPiece); int cleared = board.checkCompleteRows(); addNewPiece(); } repaint(); } public void paintComponent(Graphics g) { super.paintComponent(g); renderGame(g); } private void renderGame(Graphics g) { if (fallingPiece != null) fallingPiece.draw(g); for